Skip to content

Commit

Permalink
fix: amplitude fix for user operations (#3153)
Browse files Browse the repository at this point in the history
  • Loading branch information
utsabc authored Mar 4, 2024
2 parents 1dbde7a + 0d03c67 commit 31869fb
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 3 deletions.
13 changes: 11 additions & 2 deletions src/v0/destinations/am/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ const updateConfigProperty = (message, payload, mappingJson, validatePayload, Co
}
});
};
const identifyBuilder = (message, destination, rawPayload) => {
const userPropertiesHandler = (message, destination, rawPayload) => {
// update payload user_properties from userProperties/traits/context.traits/nested traits of Rudder message
// traits like address converted to top level user properties (think we can skip this extra processing as AM supports nesting upto 40 levels)
let traits = getFieldValueFromMessage(message, 'traits');
Expand Down Expand Up @@ -335,14 +335,16 @@ const getDefaultResponseData = (message, rawPayload, evType, groupInfo) => {
const groups = groupInfo && cloneDeep(groupInfo);
return { groups, rawPayload };
};


const getResponseData = (evType, destination, rawPayload, message, groupInfo) => {
let groups;

switch (evType) {
case EventType.IDENTIFY:
// event_type for identify event is $identify
rawPayload.event_type = IDENTIFY_AM;
rawPayload = identifyBuilder(message, destination, rawPayload);
rawPayload = userPropertiesHandler(message, destination, rawPayload);
break;
case EventType.GROUP:
// event_type for identify event is $identify
Expand All @@ -357,8 +359,15 @@ const getResponseData = (evType, destination, rawPayload, message, groupInfo) =>
case EventType.ALIAS:
break;
default:
if (destination.Config.enableEnhancedUserOperations) {
// handle all other events like track, page, screen for user properties
rawPayload = userPropertiesHandler(message, destination, rawPayload);
}
({ groups, rawPayload } = getDefaultResponseData(message, rawPayload, evType, groupInfo));
}
if (destination.Config.enableEnhancedUserOperations) {
rawPayload = AMUtils.userPropertiesPostProcess(rawPayload);
}
return { rawPayload, groups };
};

Expand Down
69 changes: 68 additions & 1 deletion src/v0/destinations/am/util.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { getUnsetObj, validateEventType } = require('./utils');
const { getUnsetObj, validateEventType, userPropertiesPostProcess } = require('./utils');

describe('getUnsetObj', () => {
it("should return undefined when 'message.integrations.Amplitude.fieldsToUnset' is not array", () => {
Expand Down Expand Up @@ -97,3 +97,70 @@ describe('validateEventType', () => {
);
});
});

describe('userPropertiesPostProcess', () => {
// The function correctly removes duplicate keys found in both operation keys and root level keys.
it('should remove duplicate keys from user_properties', () => {
// Arrange
const rawPayload = {
user_properties: {
$setOnce: {
key1: 'value1',
},
$add: {
key2: 'value2',
},
key3: 'value3',
key1: 'value4',
},
};

// Act
const result = userPropertiesPostProcess(rawPayload);

// Assert
expect(result.user_properties).toEqual({
$setOnce: {
key1: 'value1',
},
$add: {
key2: 'value2',
},
$set: {
key3: 'value3',
},
});
});

// The function correctly moves root level properties to $set operation.
it('should move root level properties to $set operation when they dont belong to any other operation', () => {
// Arrange
const rawPayload = {
user_properties: {
$setOnce: {
key1: 'value1',
},
$add: {
key2: 'value2',
},
key3: 'value3',
},
};

// Act
const result = userPropertiesPostProcess(rawPayload);

// Assert
expect(result.user_properties).toEqual({
$set: {
key3: 'value3',
},
$setOnce: {
key1: 'value1',
},
$add: {
key2: 'value2',
},
});
});
});
56 changes: 56 additions & 0 deletions src/v0/destinations/am/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,61 @@ const validateEventType = (evType) => {
);
}
};


const userPropertiesPostProcess = (rawPayload) => {
const operationList = [
'$setOnce',
'$add',
'$unset',
'$append',
'$prepend',
'$preInsert',
'$postInsert',
'$remove',
];
// eslint-disable-next-line @typescript-eslint/naming-convention
const { user_properties } = rawPayload;
const userPropertiesKeys = Object.keys(user_properties).filter(
(key) => !operationList.includes(key),
);
const duplicatekeys = new Set();
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key of userPropertiesKeys) {
// check if any of the keys are present in the user_properties $setOnce, $add, $unset, $append, $prepend, $preInsert, $postInsert, $remove keys as well as root level

if (
operationList.some(
(operation) => user_properties[operation] && user_properties[operation][key],
)
) {
duplicatekeys.add(key);
}
}
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key of duplicatekeys) {
delete user_properties[key];
}

// Moving root level properties that doesn't belong to any operation under $set
const setProps = {};
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(user_properties)) {
if (!operationList.includes(key)) {
setProps[key] = value;
delete user_properties[key];
}
}

if (Object.keys(setProps).length > 0) {
user_properties.$set = setProps;
}

// eslint-disable-next-line no-param-reassign
rawPayload.user_properties = user_properties;
return rawPayload;
};

module.exports = {
getOSName,
getOSVersion,
Expand All @@ -132,4 +187,5 @@ module.exports = {
getEventId,
getUnsetObj,
validateEventType,
userPropertiesPostProcess
};

0 comments on commit 31869fb

Please sign in to comment.