Skip to content

Commit

Permalink
feat: mixpanel set once feature onboard (#2820)
Browse files Browse the repository at this point in the history
* feat: initial commit

* feat: making sure existing functionality is intact

* fix: edits for exclusion keys

* fix: edits for supporting property paths

* fix: delete wrong test case

* fix: test cases

* fix: removed unnecessary code

* fix: adding unit test cases for trimTraits

* fix: changing the order of priority in property mapping

* fix: edited distinct id logic

* fix: small edit

* fix: review comments addressed

* fix: adding dedicated mappingJson for setOnce

* fix: adding all the fields to the dedicated json

* fix: addressing review comments

* feat: review comments addressed
  • Loading branch information
shrouti1507 authored Nov 23, 2023
1 parent 5c63d2c commit 9eda50e
Show file tree
Hide file tree
Showing 8 changed files with 665 additions and 8 deletions.
3 changes: 3 additions & 0 deletions src/v0/destinations/mp/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const ConfigCategory = {
IDENTIFY: {
name: 'MPIdentifyConfig',
},
SET_ONCE: {
name: 'MPSetOnceConfig',
},
PROFILE_ANDROID: {
name: 'MPProfilePropertiesAndroid',
},
Expand Down
122 changes: 122 additions & 0 deletions src/v0/destinations/mp/data/MPSetOnceConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
[
{
"destKey": "$created",
"sourceKeys": "createdAt",
"required": false
},
{
"destKey": "$email",
"sourceKeys": "email",
"required": false
},
{
"destKey": "$first_name",
"sourceKeys": ["firstName", "firstname", "first_name"],
"required": false
},
{
"destKey": "$last_name",
"sourceKeys": ["lastName", "lastname", "last_name"],
"required": false
},
{
"destKey": "$name",
"sourceKeys": "name",
"required": false
},
{
"destKey": "$username",
"sourceKeys": ["username", "userName"],
"required": false
},
{
"destKey": "$phone",
"sourceKeys": "phone",
"required": false
},
{
"destKey": "$avatar",
"sourceKeys": "avatar",
"required": false
},
{
"destKey": "$country_code",
"sourceKeys": ["country", "address.country"],
"required": false
},
{
"destKey": "$city",
"sourceKeys": ["city", "address.city"],
"required": false
},
{
"destKey": "$region",
"sourceKeys": ["state", "address.state", "location.region"],
"required": false
},
{
"destKey": "$unsubscribed",
"sourceKeys": "unsubscribed",
"required": false
},
{
"destKey": "$geo_source",
"sourceKeys": "location.geoSource",
"required": false
},
{
"destKey": "$timezone",
"sourceKeys": "location.timezone",
"required": false
},
{
"destKey": "$latitude",
"sourceKeys": "location.latitude",
"required": false
},
{
"destKey": "$longitude",
"sourceKeys": "location.longitude",
"required": false
},
{
"destKey": "$carrier",
"sourceKeys": "network.carrier",
"required": false
},
{
"destKey": "$manufacturer",
"sourceKeys": "device.manufacturer",
"required": false
},
{
"destKey": "$model",
"sourceKeys": "device.model",
"required": false
},
{
"destKey": "$screen_height",
"sourceKeys": "screen.height",
"required": false
},
{
"destKey": "$screen_width",
"sourceKeys": "screen.width",
"required": false
},
{
"destKey": "$wifi",
"sourceKeys": "network.wifi",
"required": false
},
{
"destKey": "$initial_referrer",
"sourceKeys": "page.initial_referrer",
"required": false
},
{
"destKey": "$initial_referring_domain",
"sourceKeys": ["page.initial_referring_domain", "page.initialReferringDomain"],
"required": false
}
]
47 changes: 41 additions & 6 deletions src/v0/destinations/mp/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
checkInvalidRtTfEvents,
handleRtTfSingleEventError,
groupEventsByType,
parseConfigArray,
} = require('../../util');
const {
ConfigCategory,
Expand All @@ -35,6 +36,7 @@ const {
combineBatchRequestsWithSameJobIds,
groupEventsByEndpoint,
batchEvents,
trimTraits,
} = require('./util');
const { CommonUtils } = require('../../../util/common');

Expand Down Expand Up @@ -226,17 +228,51 @@ const processTrack = (message, destination) => {
return returnValue;
};

const createSetOnceResponse = (message, type, destination, setOnce) => {
const payload = {
$set_once: setOnce,
$token: destination.Config.token,
$distinct_id: message.userId || message.anonymousId,
};

if (destination?.Config.identityMergeApi === 'simplified') {
payload.$distinct_id = message.userId || `$device:${message.anonymousId}`;
}

return responseBuilderSimple(payload, message, type, destination.Config);
};

const processIdentifyEvents = async (message, type, destination) => {
const messageClone = { ...message };
let seggregatedTraits = {};
const returnValue = [];
let setOnceProperties = [];

// making payload for set_once properties
if (destination.Config.setOnceProperties && destination.Config.setOnceProperties.length > 0) {
setOnceProperties = parseConfigArray(destination.Config.setOnceProperties, 'property');
seggregatedTraits = trimTraits(
messageClone.traits,
messageClone.context.traits,
setOnceProperties,
);
messageClone.traits = seggregatedTraits.traits;
messageClone.context.traits = seggregatedTraits.contextTraits;
if (Object.keys(seggregatedTraits.setOnce).length > 0) {
returnValue.push(
createSetOnceResponse(messageClone, type, destination, seggregatedTraits.setOnce),
);
}
}

// Creating the user profile
// https://developer.mixpanel.com/reference/profile-set
returnValue.push(createIdentifyResponse(message, type, destination, responseBuilderSimple));
returnValue.push(createIdentifyResponse(messageClone, type, destination, responseBuilderSimple));

if (
destination.Config?.identityMergeApi !== 'simplified' &&
message.userId &&
message.anonymousId &&
messageClone.userId &&
messageClone.anonymousId &&
isImportAuthCredentialsAvailable(destination)
) {
// If userId and anonymousId both are present and required credentials for /import
Expand All @@ -245,13 +281,13 @@ const processIdentifyEvents = async (message, type, destination) => {
const trackPayload = {
event: '$merge',
properties: {
$distinct_ids: [message.userId, message.anonymousId],
$distinct_ids: [messageClone.userId, messageClone.anonymousId],
token: destination.Config.token,
},
};
const identifyTrackResponse = responseBuilderSimple(
trackPayload,
message,
messageClone,
'merge',
destination.Config,
);
Expand Down Expand Up @@ -440,7 +476,6 @@ const processRouterDest = async (inputs, reqMetadata) => {
destination: event.destination,
};
}

let processedEvents = await process(event);
processedEvents = CommonUtils.toArray(processedEvents);
return processedEvents.map((res) => ({
Expand Down
70 changes: 70 additions & 0 deletions src/v0/destinations/mp/util.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const lodash = require('lodash');
const set = require('set-value');
const get = require('get-value');
const { InstrumentationError } = require('@rudderstack/integrations-lib');
Expand All @@ -14,6 +15,7 @@ const {
defaultBatchRequestConfig,
IsGzipSupported,
isObject,
isDefinedAndNotNullAndNotEmpty,
} = require('../../util');
const {
ConfigCategory,
Expand All @@ -26,6 +28,7 @@ const { CommonUtils } = require('../../../util/common');
const mPIdentifyConfigJson = mappingConfig[ConfigCategory.IDENTIFY.name];
const mPProfileAndroidConfigJson = mappingConfig[ConfigCategory.PROFILE_ANDROID.name];
const mPProfileIosConfigJson = mappingConfig[ConfigCategory.PROFILE_IOS.name];
const mPSetOnceConfigJson = mappingConfig[ConfigCategory.SET_ONCE.name];

/**
* this function has been used to create
Expand Down Expand Up @@ -322,6 +325,72 @@ const combineBatchRequestsWithSameJobIds = (inputBatches) => {
return combineBatches(combineBatches(inputBatches));
};

/**
* Trims the traits and contextTraits objects based on the setOnceProperties array and returns an object containing the modified traits, contextTraits, and setOnce properties.
*
* @param {object} traits - An object representing the traits.
* @param {object} contextTraits - An object representing the context traits.
* @param {string[]} setOnceProperties - An array of property paths to be considered for the setOnce transformation.
* @returns {object} - An object containing the modified traits, contextTraits, and setOnce properties.
*
* @example
* const traits = { name: 'John', age: 30 };
* const contextTraits = { country: 'USA', language: 'English', address: { city: 'New York', state: 'NY' }}};
* const setOnceProperties = ['name', 'country', 'address.city'];
*
* const result = trimTraits(traits, contextTraits, setOnceProperties);
* // Output: { traits: { age: 30 }, contextTraits: { language: 'English' }, setOnce: { $name: 'John', $country_code: 'USA', city: 'New York'} }
*/
function trimTraits(traits, contextTraits, setOnceProperties) {
let sentOnceTransformedPayload;
// Create a copy of the original traits object
const traitsCopy = { ...traits };
const contextTraitsCopy = { ...contextTraits };

// Initialize setOnce object
const setOnceEligible = {};

// Step 1: find the k-v pairs of setOnceProperties in traits and contextTraits

setOnceProperties.forEach((propertyPath) => {
const propName = lodash.last(propertyPath.split('.'));

const traitsValue = get(traitsCopy, propertyPath);
const contextTraitsValue = get(contextTraitsCopy, propertyPath);

if (isDefinedAndNotNullAndNotEmpty(traitsValue)) {
setOnceEligible[propName] = traitsValue;
lodash.unset(traitsCopy, propertyPath);
}
if (isDefinedAndNotNullAndNotEmpty(contextTraitsValue)) {
if (!setOnceEligible.hasOwnProperty(propName)) {
setOnceEligible[propName] = contextTraitsValue;
}
lodash.unset(contextTraitsCopy, propertyPath);
}
});

if (setOnceEligible && Object.keys(setOnceEligible).length > 0) {
// Step 2: transform properties eligible as per rudderstack declared identify event mapping
// setOnce should have all traits from message.traits and message.context.traits by now
sentOnceTransformedPayload = constructPayload(setOnceEligible, mPSetOnceConfigJson);

// Step 3: combine the transformed and custom setOnce traits
sentOnceTransformedPayload = extractCustomFields(
setOnceEligible,
sentOnceTransformedPayload,
'root',
MP_IDENTIFY_EXCLUSION_LIST,
);
}

return {
traits: traitsCopy,
contextTraits: contextTraitsCopy,
setOnce: sentOnceTransformedPayload || {},
};
}

module.exports = {
createIdentifyResponse,
isImportAuthCredentialsAvailable,
Expand All @@ -330,4 +399,5 @@ module.exports = {
generateBatchedPayloadForArray,
batchEvents,
combineBatchRequestsWithSameJobIds,
trimTraits,
};
Loading

0 comments on commit 9eda50e

Please sign in to comment.