Skip to content

Commit

Permalink
feat: add support for interaction events in sfmc (#3109)
Browse files Browse the repository at this point in the history
* feat: add support for interaction events in sfmc

* chore: refactorx1

* chore: fix response structure

* chore: add test

* chore: map contect key from properties instead of userId

* chore: add component test
  • Loading branch information
yashasvibajpai authored Feb 27, 2024
1 parent 75e9f46 commit 0486049
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/v0/destinations/sfmc/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const ENDPOINTS = {
GET_TOKEN: `auth.marketingcloudapis.com/v2/token`,
CONTACTS: `rest.marketingcloudapis.com/contacts/v1/contacts`,
INSERT_CONTACTS: `rest.marketingcloudapis.com/hub/v1/dataevents/key:`,
EVENT: "rest.marketingcloudapis.com/interaction/v1/events",
};

const CONFIG_CATEGORIES = {
Expand Down
35 changes: 33 additions & 2 deletions src/v0/destinations/sfmc/transform.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
/* eslint-disable no-nested-ternary */
const {
NetworkError,
Expand Down Expand Up @@ -188,6 +189,26 @@ const responseBuilderForInsertData = (
return response;
};

// DOC : https://developer.salesforce.com/docs/marketing/marketing-cloud/references/mc_rest_interaction/postEvent.html

const responseBuilderForMessageEvent = (message, subDomain, authToken, hashMapEventDefinition) => {
const contactKey = message.properties.contactId;
delete message.properties.contactId;
const response = defaultRequestConfig();
response.method = defaultPostRequestConfig.requestMethod;
response.endpoint = `https://${subDomain}.${ENDPOINTS.EVENT}`;
response.headers = {
'Content-Type': JSON_MIME_TYPE,
Authorization: `Bearer ${authToken}`,
};
response.body.JSON = {
ContactKey: contactKey,
EventDefinitionKey: hashMapEventDefinition[message.event.toLowerCase()],
Data: { ...message.properties },
};
return response;
};

const responseBuilderSimple = async (message, category, destination) => {
const {
clientId,
Expand All @@ -198,6 +219,7 @@ const responseBuilderSimple = async (message, category, destination) => {
eventToExternalKey,
eventToPrimaryKey,
eventToUUID,
eventToDefinitionMapping,
} = destination.Config;
// map from an event name to an external key of a data extension.
const hashMapExternalKey = getHashFromArray(eventToExternalKey, 'from', 'to');
Expand All @@ -207,6 +229,8 @@ const responseBuilderSimple = async (message, category, destination) => {
const hashMapUUID = getHashFromArray(eventToUUID, 'event', 'uuid');
// token needed for authorization for subsequent calls
const authToken = await getToken(clientId, clientSecret, subDomain);
// map from an event name to an event definition key.
const hashMapEventDefinition = getHashFromArray(eventToDefinitionMapping, 'from', 'to');
// if createOrUpdateContacts is true identify calls for create and update of contacts will not occur.
if (category.type === 'identify' && !createOrUpdateContacts) {
// first call to identify the contact
Expand Down Expand Up @@ -240,10 +264,12 @@ const responseBuilderSimple = async (message, category, destination) => {
if (typeof message.event !== 'string') {
throw new ConfigurationError('Event name must be a string');
}
if (hashMapEventDefinition[message.event.toLowerCase()]) {
return responseBuilderForMessageEvent(message, subDomain, authToken, hashMapEventDefinition);
}
if (!isDefinedAndNotNull(hashMapExternalKey[message.event.toLowerCase()])) {
throw new ConfigurationError('Event not mapped for this track call');
}

return responseBuilderForInsertData(
message,
hashMapExternalKey[message.event.toLowerCase()],
Expand Down Expand Up @@ -293,4 +319,9 @@ const processRouterDest = async (inputs, reqMetadata) => {
return respList;
};

module.exports = { process, processRouterDest, responseBuilderSimple };
module.exports = {
process,
processRouterDest,
responseBuilderSimple,
responseBuilderForMessageEvent,
};
42 changes: 41 additions & 1 deletion src/v0/destinations/sfmc/transform.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { ConfigurationError } = require('@rudderstack/integrations-lib');
const axios = require('axios');
const MockAxiosAdapter = require('axios-mock-adapter');
const { responseBuilderSimple } = require('./transform');
const { responseBuilderSimple, responseBuilderForMessageEvent } = require('./transform');
beforeAll(() => {
const mock = new MockAxiosAdapter(axios);
mock
Expand Down Expand Up @@ -122,4 +122,44 @@ describe('responseBuilderSimple', () => {
expect(response).toHaveProperty('body.JSON');
expect(response).toHaveProperty('headers');
});

it('should build response object with correct details for message event', () => {
const message = {
userId: 'u123',
event: 'testEvent',
properties: {
contactId: '12345',
prop1: 'value1',
prop2: 'value2',
},
};
const subDomain = 'subdomain';
const authToken = 'token';
const hashMapEventDefinition = {
testevent: 'eventDefinitionKey',
};

const response = responseBuilderForMessageEvent(
message,
subDomain,
authToken,
hashMapEventDefinition,
);
expect(response.method).toBe('POST');
expect(response.endpoint).toBe(
'https://subdomain.rest.marketingcloudapis.com/interaction/v1/events',
);
expect(response.headers).toEqual({
'Content-Type': 'application/json',
Authorization: 'Bearer token',
});
expect(response.body.JSON).toEqual({
ContactKey: '12345',
EventDefinitionKey: 'eventDefinitionKey',
Data: {
prop1: 'value1',
prop2: 'value2',
},
});
});
});
162 changes: 162 additions & 0 deletions test/integrations/destinations/sfmc/processor/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1732,4 +1732,166 @@ export const data = [
},
},
},
{
name: 'sfmc',
description: 'Test 12',
feature: 'processor',
module: 'destination',
version: 'v0',
input: {
request: {
body: [
{
message: {
event: 'message event',
context: {
app: {
build: '1.0.0',
name: 'RudderLabs JavaScript SDK',
version: '1.0.0',
},
campaign: {
name: 'Demo Campaign',
source: 'facebook',
medium: 'online',
term: 'Demo terms',
content: 'Demo content',
},
traits: {
email: '[email protected]',
name: 'Tonmoy Labs',
},
library: {
name: 'RudderLabs JavaScript SDK',
version: '1.0.0',
},
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
locale: 'en-GB',
ip: '0.0.0.0',
screen: {
density: 2,
height: 860,
width: 1280,
},
},
type: 'track',
userId: '12345',
properties: {
id: 'id101',
contactId: 'cid101',
email: '[email protected]',
accountNumber: '99110099',
patronName: 'SP',
},
sentAt: '2019-10-14T09:03:22.563Z',
integrations: {
All: true,
},
},
destination: {
ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
Name: 'SFMC',
DestinationDefinition: {
ID: '1pYpYSeQd8OeN6xPdw6VGDzqUd1',
Name: 'SFMC',
DisplayName: 'Salesforce Marketing Cloud',
Config: {
destConfig: [],
excludeKeys: [],
includeKeys: [],
saveDestinationResponse: false,
supportedSourceTypes: [],
transformAt: 'processor',
},
ResponseRules: {},
},
Config: {
clientId: 'vcn7AQ2W9GGIAZSsN6Mfq',
clientSecret: 'vcn7AQ2W9GGIAZSsN6Mfq',
createOrUpdateContacts: false,
eventDelivery: true,
eventDeliveryTS: 1615371070621,
eventToExternalKey: [
{
from: 'Event Name',
to: 'C500FD37-155C-49BD-A21B-AFCEF3D1A9CB',
},
{
from: 'Watch',
to: 'C500FD37-155C-49BD-A21B-AFCEF3D1A9CB',
},
],
eventToPrimaryKey: [
{
from: 'userId',
to: 'User Key',
},
{
from: 'watch',
to: 'Guest Key, Contact Key',
},
],
eventToUUID: [
{
event: 'Event Name',
uuid: true,
},
],
eventToDefinitionMapping: [
{
from: 'message event',
to: 'test-event-definition',
},
],
externalKey: 'f3ffa19b-e0b3-4967-829f-549b781080e6',
subDomain: 'vcn7AQ2W9GGIAZSsN6Mfq',
},
Enabled: true,
Transformations: [],
},
},
],
},
},
output: {
response: {
status: 200,
body: [
{
output: {
body: {
XML: {},
JSON_ARRAY: {},
FORM: {},
JSON: {
ContactKey: 'cid101',
Data: {
accountNumber: '99110099',
email: '[email protected]',
id: 'id101',
patronName: 'SP',
},
EventDefinitionKey: 'test-event-definition',
},
},
type: 'REST',
files: {},
method: 'POST',
params: {},
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer yourAuthToken',
},
version: '1',
endpoint:
'https://vcn7AQ2W9GGIAZSsN6Mfq.rest.marketingcloudapis.com/interaction/v1/events',
userId: '',
},
statusCode: 200,
},
],
},
},
},
];

0 comments on commit 0486049

Please sign in to comment.