From 55f96374b4d73db7013c1d5e72bfc9c8257b224b Mon Sep 17 00:00:00 2001
From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com>
Date: Mon, 6 Nov 2023 12:48:38 +0530
Subject: [PATCH 1/2] feat: onboard revenuecat as a source (#2774)
* refactor: revenuecat source code
* chore: refactor, address comments
---
src/v0/sources/revenuecat/mapping.json | 10 +
src/v0/sources/revenuecat/transform.js | 47 +++
test/integrations/sources/revenuecat/data.ts | 286 +++++++++++++++++++
3 files changed, 343 insertions(+)
create mode 100644 src/v0/sources/revenuecat/mapping.json
create mode 100644 src/v0/sources/revenuecat/transform.js
create mode 100644 test/integrations/sources/revenuecat/data.ts
diff --git a/src/v0/sources/revenuecat/mapping.json b/src/v0/sources/revenuecat/mapping.json
new file mode 100644
index 0000000000..541568b71b
--- /dev/null
+++ b/src/v0/sources/revenuecat/mapping.json
@@ -0,0 +1,10 @@
+[
+ {
+ "sourceKeys": "event.type",
+ "destKeys": "event"
+ },
+ {
+ "sourceKeys": "event.id",
+ "destKeys": "messageId"
+ }
+]
diff --git a/src/v0/sources/revenuecat/transform.js b/src/v0/sources/revenuecat/transform.js
new file mode 100644
index 0000000000..36944e10fa
--- /dev/null
+++ b/src/v0/sources/revenuecat/transform.js
@@ -0,0 +1,47 @@
+const { camelCase } = require('lodash');
+const moment = require('moment');
+const { removeUndefinedAndNullValues, isDefinedAndNotNull } = require('../../util');
+const Message = require('../message');
+
+function process(event) {
+ const message = new Message(`RevenueCat`);
+
+ // we are setting event type as track always
+ message.setEventType('track');
+
+ const properties = {};
+ // dump all event properties to message.properties after converting them to camelCase
+ if (event.event) {
+ Object.keys(event.event).forEach((key) => {
+ properties[camelCase(key)] = event.event[key];
+ });
+ message.setProperty('properties', properties);
+ }
+
+ // setting up app_user_id to externalId : revenuecatAppUserId
+ if (event?.event?.app_user_id) {
+ message.context.externalId = [
+ {
+ type: 'revenuecatAppUserId',
+ id: event?.event?.app_user_id,
+ },
+ ];
+ }
+
+ if (
+ isDefinedAndNotNull(event?.event?.event_timestamp_ms) &&
+ moment(event?.event?.event_timestamp_ms).isValid()
+ ) {
+ const validTimestamp = new Date(event.event.event_timestamp_ms).toISOString();
+ message.setProperty('originalTimestamp', validTimestamp);
+ message.setProperty('sentAt', validTimestamp);
+ }
+ message.event = event?.event?.type;
+ message.messageId = event?.event?.id;
+
+ // removing undefined and null values from message
+ removeUndefinedAndNullValues(message);
+ return message;
+}
+
+module.exports = { process };
diff --git a/test/integrations/sources/revenuecat/data.ts b/test/integrations/sources/revenuecat/data.ts
new file mode 100644
index 0000000000..4963781763
--- /dev/null
+++ b/test/integrations/sources/revenuecat/data.ts
@@ -0,0 +1,286 @@
+export const data = [
+ {
+ name: 'revenuecat',
+ description: 'Simple track call',
+ module: 'source',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ api_version: '1.0',
+ event: {
+ aliases: [
+ 'f8e14f51-0c76-49ba-8d67-c229f1875dd9',
+ '389ad6dd-bb40-4c03-9471-1353da2d55ec',
+ ],
+ app_user_id: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9',
+ commission_percentage: null,
+ country_code: 'US',
+ currency: null,
+ entitlement_id: null,
+ entitlement_ids: null,
+ environment: 'SANDBOX',
+ event_timestamp_ms: 1698617217232,
+ expiration_at_ms: 1698624417232,
+ id: '8CF0CD6C-CAF3-41FB-968A-661938235AF0',
+ is_family_share: null,
+ offer_code: null,
+ original_app_user_id: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9',
+ original_transaction_id: null,
+ period_type: 'NORMAL',
+ presented_offering_id: null,
+ price: null,
+ price_in_purchased_currency: null,
+ product_id: 'test_product',
+ purchased_at_ms: 1698617217232,
+ store: 'APP_STORE',
+ subscriber_attributes: {
+ $displayName: {
+ updated_at_ms: 1698617217232,
+ value: 'Mister Mistoffelees',
+ },
+ $email: {
+ updated_at_ms: 1698617217232,
+ value: 'tuxedo@revenuecat.com',
+ },
+ $phoneNumber: {
+ updated_at_ms: 1698617217232,
+ value: '+19795551234',
+ },
+ my_custom_attribute_1: {
+ updated_at_ms: 1698617217232,
+ value: 'catnip',
+ },
+ },
+ takehome_percentage: null,
+ tax_percentage: null,
+ transaction_id: null,
+ type: 'TEST',
+ },
+ },
+ ],
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ pathSuffix: '',
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ batch: [
+ {
+ context: {
+ library: {
+ name: 'unknown',
+ version: 'unknown',
+ },
+ integration: {
+ name: 'RevenueCat',
+ },
+ externalId: [
+ {
+ type: 'revenuecatAppUserId',
+ id: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9',
+ },
+ ],
+ },
+ integrations: {
+ RevenueCat: false,
+ },
+ type: 'track',
+ properties: {
+ aliases: [
+ 'f8e14f51-0c76-49ba-8d67-c229f1875dd9',
+ '389ad6dd-bb40-4c03-9471-1353da2d55ec',
+ ],
+ appUserId: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9',
+ commissionPercentage: null,
+ countryCode: 'US',
+ currency: null,
+ entitlementId: null,
+ entitlementIds: null,
+ environment: 'SANDBOX',
+ eventTimestampMs: 1698617217232,
+ expirationAtMs: 1698624417232,
+ id: '8CF0CD6C-CAF3-41FB-968A-661938235AF0',
+ isFamilyShare: null,
+ offerCode: null,
+ originalAppUserId: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9',
+ originalTransactionId: null,
+ periodType: 'NORMAL',
+ presentedOfferingId: null,
+ price: null,
+ priceInPurchasedCurrency: null,
+ productId: 'test_product',
+ purchasedAtMs: 1698617217232,
+ store: 'APP_STORE',
+ subscriberAttributes: {
+ $displayName: {
+ updated_at_ms: 1698617217232,
+ value: 'Mister Mistoffelees',
+ },
+ $email: {
+ updated_at_ms: 1698617217232,
+ value: 'tuxedo@revenuecat.com',
+ },
+ $phoneNumber: {
+ updated_at_ms: 1698617217232,
+ value: '+19795551234',
+ },
+ my_custom_attribute_1: {
+ updated_at_ms: 1698617217232,
+ value: 'catnip',
+ },
+ },
+ takehomePercentage: null,
+ taxPercentage: null,
+ transactionId: null,
+ type: 'TEST',
+ },
+ event: 'TEST',
+ messageId: '8CF0CD6C-CAF3-41FB-968A-661938235AF0',
+ originalTimestamp: '2023-10-29T22:06:57.232Z',
+ sentAt: '2023-10-29T22:06:57.232Z',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'revenuecat',
+ description: 'Initial purchase event',
+ module: 'source',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ api_version: '1.0',
+ event: {
+ aliases: ['yourCustomerAliasedID', 'yourCustomerAliasedID'],
+ app_id: 'yourAppID',
+ app_user_id: 'yourCustomerAppUserID',
+ commission_percentage: 0.3,
+ country_code: 'US',
+ currency: 'USD',
+ entitlement_id: 'pro_cat',
+ entitlement_ids: ['pro_cat'],
+ environment: 'PRODUCTION',
+ event_timestamp_ms: 1591121855319,
+ expiration_at_ms: 1591726653000,
+ id: 'UniqueIdentifierOfEvent',
+ is_family_share: false,
+ offer_code: 'free_month',
+ original_app_user_id: 'OriginalAppUserID',
+ original_transaction_id: '1530648507000',
+ period_type: 'NORMAL',
+ presented_offering_id: 'OfferingID',
+ price: 2.49,
+ price_in_purchased_currency: 2.49,
+ product_id: 'onemonth_no_trial',
+ purchased_at_ms: 1591121853000,
+ store: 'APP_STORE',
+ subscriber_attributes: {
+ '$Favorite Cat': {
+ updated_at_ms: 1581121853000,
+ value: 'Garfield',
+ },
+ },
+ takehome_percentage: 0.7,
+ tax_percentage: 0.3,
+ transaction_id: '170000869511114',
+ type: 'INITIAL_PURCHASE',
+ },
+ },
+ ],
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ pathSuffix: '',
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ batch: [
+ {
+ context: {
+ library: {
+ name: 'unknown',
+ version: 'unknown',
+ },
+ integration: {
+ name: 'RevenueCat',
+ },
+ externalId: [
+ {
+ type: 'revenuecatAppUserId',
+ id: 'yourCustomerAppUserID',
+ },
+ ],
+ },
+ integrations: {
+ RevenueCat: false,
+ },
+ type: 'track',
+ properties: {
+ aliases: ['yourCustomerAliasedID', 'yourCustomerAliasedID'],
+ appId: 'yourAppID',
+ appUserId: 'yourCustomerAppUserID',
+ commissionPercentage: 0.3,
+ countryCode: 'US',
+ currency: 'USD',
+ entitlementId: 'pro_cat',
+ entitlementIds: ['pro_cat'],
+ environment: 'PRODUCTION',
+ eventTimestampMs: 1591121855319,
+ expirationAtMs: 1591726653000,
+ id: 'UniqueIdentifierOfEvent',
+ isFamilyShare: false,
+ offerCode: 'free_month',
+ originalAppUserId: 'OriginalAppUserID',
+ originalTransactionId: '1530648507000',
+ periodType: 'NORMAL',
+ presentedOfferingId: 'OfferingID',
+ price: 2.49,
+ priceInPurchasedCurrency: 2.49,
+ productId: 'onemonth_no_trial',
+ purchasedAtMs: 1591121853000,
+ store: 'APP_STORE',
+ subscriberAttributes: {
+ '$Favorite Cat': {
+ updated_at_ms: 1581121853000,
+ value: 'Garfield',
+ },
+ },
+ takehomePercentage: 0.7,
+ taxPercentage: 0.3,
+ transactionId: '170000869511114',
+ type: 'INITIAL_PURCHASE',
+ },
+ event: 'INITIAL_PURCHASE',
+ messageId: 'UniqueIdentifierOfEvent',
+ originalTimestamp: '2020-06-02T18:17:35.319Z',
+ sentAt: '2020-06-02T18:17:35.319Z',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+];
From 6e89cd3f67ea887ba17c1cd5ffbca6675f54d96c Mon Sep 17 00:00:00 2001
From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com>
Date: Mon, 6 Nov 2023 12:55:19 +0530
Subject: [PATCH 2/2] fix: add check to remove null and undefined properties
before sending (#2796)
* fix: add check to remove null and undefined properties before sending
* chore: fix test
---
src/v0/destinations/adobe_analytics/transform.js | 3 ++-
test/__tests__/data/adobe_analytics.json | 4 ++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/v0/destinations/adobe_analytics/transform.js b/src/v0/destinations/adobe_analytics/transform.js
index 54806bf578..67bb66310a 100644
--- a/src/v0/destinations/adobe_analytics/transform.js
+++ b/src/v0/destinations/adobe_analytics/transform.js
@@ -11,6 +11,7 @@ const {
isDefinedAndNotNull,
isDefinedAndNotNullAndNotEmpty,
getIntegrationsObj,
+ removeUndefinedAndNullValues,
simpleProcessRouterDest,
} = require('../../util');
const {
@@ -394,7 +395,7 @@ const handleTrack = (message, destinationConfig) => {
break;
}
- return payload;
+ return removeUndefinedAndNullValues(payload);
};
const process = async (event) => {
diff --git a/test/__tests__/data/adobe_analytics.json b/test/__tests__/data/adobe_analytics.json
index cfffccb8da..6361f92640 100644
--- a/test/__tests__/data/adobe_analytics.json
+++ b/test/__tests__/data/adobe_analytics.json
@@ -1010,7 +1010,6 @@
},
"messageId": "1578564113557-af022c68-429e-4af4-b99b-2b9174056383",
"properties": {
- "order_id": "1234",
"affiliation": "Apple Store",
"value": 20,
"revenue": 15.0,
@@ -1019,6 +1018,7 @@
"discount": 1.5,
"coupon": "ImagePro",
"currency": "USD",
+ "purchaseId": "p101",
"products": [
{
"product_id": "123",
@@ -1205,7 +1205,7 @@
"JSON": {},
"JSON_ARRAY": {},
"XML": {
- "payload": "17941080sales campaignwebUSD127.0.0.1en-US12341234Dalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerroottval001RudderLabs JavaScript SDKocheckout startedhttps://www.estore.com/best-seller/12020-01-09T10:01:53.558Zmktcloudid001scCheckoutGames;Monopoly;1;14.00,Games;UNO;2;6.90footlockerrudderstackpoc"
+ "payload": "17941080sales campaignwebUSD127.0.0.1en-USDalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerroottval001RudderLabs JavaScript SDKocheckout startedhttps://www.estore.com/best-seller/12020-01-09T10:01:53.558Zmktcloudid001p101scCheckoutGames;Monopoly;1;14.00,Games;UNO;2;6.90footlockerrudderstackpoc"
},
"FORM": {}
},