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(new integration): Iterable - onboard integration #670

Merged
merged 9 commits into from
Oct 11, 2022
203 changes: 203 additions & 0 deletions integrations/Iterable/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/* eslint-disable class-methods-use-this */
import get from "get-value";
import logger from "../../utils/logUtil";
import { formPurchaseEventPayload, existsInMapping } from "./utils";

import {
isDefinedAndNotNull,
removeUndefinedAndNullValues,
isNotEmpty,
} from "../utils/commonUtils";
import { NAME } from "./constants";
import ScriptLoader from "../ScriptLoader";

class Iterable {
constructor(config) {
MoumitaM marked this conversation as resolved.
Show resolved Hide resolved
this.apiKey = config.apiKey;
this.initialisationIdentifier = config.initialisationIdentifier;
this.fetchAppEvents = undefined;
this.name = NAME;
this.getInAppEventMapping = config.getInAppEventMapping;
this.purchaseEventMapping = config.purchaseEventMapping;

this.sendTrackForInapp = config.sendTrackForInapp;
this.animationDuration = config.animationDuration;
this.displayInterval = config.displayInterval;
this.onOpenScreenReaderMessage = config.onOpenScreenReaderMessage;
this.onOpenNodeToTakeFocus = config.onOpenNodeToTakeFocus;
this.packageName = config.packageName;
this.rightOffset = config.rightOffset;
this.topOffset = config.topOffset;
this.bottomOffset = config.bottomOffset;
this.handleLinks = config.handleLinks;

this.closeButtonColor = config.closeButtonColor;
this.closeButtonSize = config.closeButtonSize;
this.closeButtonColorTopOffset = config.closeButtonColorTopOffset;
this.closeButtonColorSideOffset = config.closeButtonColorSideOffset;
this.iconPath = config.iconPath;
this.isRequiredToDismissMessage = config.isRequiredToDismissMessage;
this.closeButtonPosition = config.closeButtonPosition;
}

init() {
logger.debug("===In init Iterable===");
ScriptLoader("iterable-web","https://unpkg.com/@iterable/web-sdk/index.js")
}

isLoaded() {
logger.debug("===In isLoaded Iterable===");
return !!window['@iterable/web-sdk'];
}

isReady() {
logger.debug("===In isReady Iterable===");
return !!window['@iterable/web-sdk'];
}

identify(rudderElement) {
logger.debug("===In identify Iterable");

const { message } = rudderElement;
const { integrations } = message;
const userEmail = message.traits?.email || message.context?.traits?.email;
const userId = message.userId;

async function extractJWT (message) {
if (integrations && integrations.ITERABLE) {
const { jwt_token } = integrations.ITERABLE;
if (isDefinedAndNotNull(jwt_token))
return jwt_token;
} else {
logger.error("The JWT token was not passed, The SDK could not be initialised.")
return;
}
}

// Initialize the iterable SDK with the proper apiKey and the passed JWT
function initializeSDK(initialisationIdentifier, apiKey) {
let wd = window['@iterable/web-sdk'].initialize(apiKey, extractJWT)
if (initialisationIdentifier === "email") {
wd.setEmail(userEmail).then(() => {
logger.debug("userEmail set");
});
} else {
wd.setUserID(userId).then(() => {
logger.debug("userId set");
});
}
}

switch (this.initialisationIdentifier) {
case "email":
initializeSDK(this.initialisationIdentifier, this.apiKey)
break;
case "userId":
initializeSDK(this.initialisationIdentifier, this.apiKey)
break;
default:
initializeSDK("email")
break;
}
utsabc marked this conversation as resolved.
Show resolved Hide resolved
/* Available pop-up push notification settings configurable from UI
this.animationDuration,
this.displayInterval,
this.onOpenScreenReaderMessage,
this.onOpenNodeToTakeFocus,
this.packageName,
this.rightOffset,
this.topOffset,
this.bottomOffset,
this.handleLinks,
this.closeButtonColor,
this.closeButtonSize,
this.closeButtonColorTopOffset,
this.closeButtonColorSideOffset,
this.iconPath,
this.isRequiredToDismissMessage,
this.closeButtonPosition,
*/
// Reference : https://github.com/iterable/iterable-web-sdk
let getInAppMessagesPayload = {
count: 20,
animationDuration: Number(this.animationDuration) || 400,
displayInterval: Number(this.displayInterval) || 30000,
onOpenScreenReaderMessage: this.onOpenScreenReaderMessage || undefined,
onOpenNodeToTakeFocus: this.onOpenNodeToTakeFocus || undefined,
packageName: this.packageName || undefined,
rightOffset: this.rightOffset || undefined,
topOffset: this.topOffset || undefined,
bottomOffset: this.bottomOffset || undefined,
handleLinks: this.handleLinks || undefined,
closeButton: {
color: this.closeButtonColor || 'red',
size: this.closeButtonSize || '16px',
topOffset: this.closeButtonColorTopOffset || '4%',
sideOffset: this.closeButtonColorSideOffset || '4%',
iconPath: this.iconPath || undefined,
isRequiredToDismissMessage: this.isRequiredToDismissMessage || undefined,
position: this.closeButtonPosition || 'top-right'
}
}
getInAppMessagesPayload = removeUndefinedAndNullValues(getInAppMessagesPayload);

const { request } = window['@iterable/web-sdk'].getInAppMessages(
getInAppMessagesPayload,
{ display: 'immediate' }
);
// fetchAppEvents is a class function now available throughout
// we will trigger getInAppMessages when event name matches from the ui mapping in config.
this.fetchAppEvents = request;
}

track(rudderElement) {
logger.debug("===In track Iterable===");

const { message } = rudderElement;
const { event } = message;
const eventPayload = removeUndefinedAndNullValues(message.properties);
const userEmail = get(message, "context.traits.email");
const userId = get(message, "userId");
if (!event) {
logger.error("Event name not present");
return;
}
if (isNotEmpty(this.getInAppEventMapping) && existsInMapping(this.getInAppEventMapping, event)) {
this.fetchAppEvents();
// send a track call for getinappMessages if option enabled in config
if (this.sendTrackForInapp) {
window['@iterable/web-sdk'].track({ email: userEmail, userId, eventName: "Track getInAppMessages", dataFields: eventPayload })
.then(logger.debug("Web in-app push triggered"));
}
}
else if (isNotEmpty(this.purchaseEventMapping) && existsInMapping(this.purchaseEventMapping, event)) {
utsabc marked this conversation as resolved.
Show resolved Hide resolved
// purchase events
const purchaseEventPayload = formPurchaseEventPayload(message)
window['@iterable/web-sdk'].trackPurchase(
purchaseEventPayload,
)
} else {
// custom events if event is not mapped
/* fields available for custom track event
{
"email": "string",
"userId": "string",
"eventName": "string",
"id": "string",
"createdAt": 0,
"dataFields": {},
"campaignId": 0,
"templateId": 0
}
*/
// Either email or userId must be passed in to identify the user.
// If both are passed in, email takes precedence.
logger.debug(`The event ${event} is not mapped in the dashboard, firing a custom event`);
window['@iterable/web-sdk'].track({ email: userEmail, userId, eventName: event, dataFields: eventPayload })
utsabc marked this conversation as resolved.
Show resolved Hide resolved
.then(logger.debug("Track a custom event."));

}
}
}

export default Iterable;
8 changes: 8 additions & 0 deletions integrations/Iterable/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const NAME = "ITERABLE";
const CNameMapping = {
[NAME]: NAME,
Iterable: NAME,
iterable: NAME,
};

export { NAME, CNameMapping };
3 changes: 3 additions & 0 deletions integrations/Iterable/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Iterable from "./browser";

export default Iterable;
62 changes: 62 additions & 0 deletions integrations/Iterable/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { getDataFromSource } from "../../utils/utils";


const ITEMS_MAPPING = [
{ src: "product_id", dest: "id" },
{ src: "sku", dest: "sku" },
{ src: "name", dest: "name" },
{ src: "price", dest: "price" },
{ src: "quantity", dest: "quantity" },
{ src: "image_url", dest: "imageUrl" },
{ src: "url", dest: "url" }
]

function getMappingObject (properties, mappings) {
let itemsObject = {};
mappings.forEach((mapping) => {
itemsObject = {
...getDataFromSource(mapping.src, mapping.dest, properties),
...itemsObject,
};
});
return itemsObject;
}

function formPurchaseEventPayload(message) {
let purchaseEventPayload = {};
const { products } = message.properties;
utsabc marked this conversation as resolved.
Show resolved Hide resolved
purchaseEventPayload.id = message.properties.order_id || message.properties.checkout_id;
purchaseEventPayload.total = message.properties.total;
purchaseEventPayload.items = [];
const lineItems = [];
if (products) {
products.forEach((p) => {
const product = getMappingObject(p, ITEMS_MAPPING);
lineItems.push(product);
});
} else {
// if product related info is on properties root
let product = {};
product.id = message.properties.product_id;
product.sku = message.properties.sku;
product.name = message.properties.name;
product.price = message.properties.price;
product.quantity = message.properties.quantity;
product.imageUrl = message.properties.image_url;
product.url = message.properties.url;
lineItems.push(product);
}
purchaseEventPayload.items = lineItems;
return purchaseEventPayload;
}

function existsInMapping(mappedEvents, event) {
let mapped = false;
mappedEvents.forEach((e) => {
if (e.eventName == event)
mapped = true;
})
return mapped;
}

export { formPurchaseEventPayload, existsInMapping };
1 change: 1 addition & 0 deletions integrations/client_server_name.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const clientToServerNames = {
WOOPRA: "Woopra",
ROLLBAR: "RollBar",
QUORA_PIXEL: "Quora Pixel",
ITERABLE: "Iterable",
ENGAGE: "Engage",
JUNE: "June",
};
Expand Down
2 changes: 2 additions & 0 deletions integrations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import * as Shynet from "./Shynet";
import * as Woopra from "./Woopra";
import * as RollBar from "./RollBar";
import * as QuoraPixel from "./QuoraPixel";
import * as Iterable from "./Iterable";
import * as Engage from "./Engage";
import * as June from "./June";

Expand Down Expand Up @@ -123,6 +124,7 @@ const integrations = {
WOOPRA: Woopra.default,
ROLLBAR: RollBar.default,
QUORA_PIXEL: QuoraPixel.default,
ITERABLE: Iterable.default,
ENGAGE: Engage.default,
JUNE: June.default,
};
Expand Down
2 changes: 2 additions & 0 deletions integrations/integration_cname.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { CNameMapping as Shynet } from "./Shynet/constants";
import { CNameMapping as Woopra } from "./Woopra/constants";
import { CNameMapping as RollBar } from "./RollBar/constants";
import { CNameMapping as QuoraPixel } from "./QuoraPixel/constants";
import { CNameMapping as Iterable } from "./Iterable/constants";
import { CNameMapping as Engage } from "./Engage/constants";
import { CNameMapping as June } from "./June/constants";

Expand Down Expand Up @@ -123,6 +124,7 @@ const commonNames = {
...Woopra,
...RollBar,
...QuoraPixel,
...Iterable,
...Engage,
...June,
};
Expand Down