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

fix: ga4 allow zero #1915

Merged
merged 6 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
isReservedEventName,
getCustomParameters,
formatAndValidateEventName,
prepareStandardEventParams,
} from '../../../src/integrations/GA4/utils';

import {
Expand Down Expand Up @@ -338,43 +339,221 @@ describe('Google Analytics 4 utilities tests', () => {

describe('filterUserTraits function tests', () => {
it('Should update mentioned PII keys value to null', () => {
const piiPropertiesToIgnore = [{ piiProperty: 'email' }, { piiProperty: 'phone' }, { piiProperty: 'card_number' }];
const piiPropertiesToIgnore = [
{ piiProperty: 'email' },
{ piiProperty: 'phone' },
{ piiProperty: 'card_number' },
];

const userTraits = {
name: 'SDK Test',
email: '[email protected]',
card_number: '123456',
phone: '123456789',
country: 'usa'
}
country: 'usa',
};

const filteredUserTraits = {
name: 'SDK Test',
country: 'usa',
email: null,
card_number: null,
phone: null
phone: null,
};

const result = filterUserTraits(piiPropertiesToIgnore, userTraits);
expect(result).toEqual(filteredUserTraits);
});

it('Should override values of pii fields to null', () => {
const piiPropertiesToIgnore = [{ piiProperty: 'email' }, { piiProperty: 'name' }, { piiProperty: 'isPaid' }, { piiProperty: undefined }, { piiProperty: {} }];
const piiPropertiesToIgnore = [
{ piiProperty: 'email' },
{ piiProperty: 'name' },
{ piiProperty: 'isPaid' },
{ piiProperty: undefined },
{ piiProperty: {} },
];

const userTraits = {
name: 'SDK Test',
email: '[email protected]',
isPaid: false
}
isPaid: false,
};

const result = filterUserTraits(piiPropertiesToIgnore, userTraits);
expect(result).toEqual({
name: null,
email: null,
isPaid: null
isPaid: null,
});
});
});
});

describe('prepareStandardEventParams function tests', () => {
it('Should not fail when values are 0', () => {
const eventConfig = {
event: 'UserSignup',
mapping: [
{ sourceKeys: ['properties.total'], destKey: 'order_total', required: true },
{ sourceKeys: ['properties.value'], destKey: 'order_value', required: true },
{ sourceKeys: ['properties.revenue'], destKey: 'order_revenue', required: true },
{ sourceKeys: ['properties.price'], destKey: 'order_price', required: true },
],
};
const message = {
properties: {
total: 0, // Total is 0 but should pass without failure
value: 0, // Value is 0 but should pass without failure
revenue: 0, // Revenue is 0 but should pass without failure
price: 0, // Price is 0 but should pass without failure
},
event: 'UserSignup',
};
const result = prepareStandardEventParams(message, eventConfig);
expect(result).toEqual({
order_total: 0,
order_value: 0,
order_revenue: 0,
order_price: 0,
});
});

it('Should handle mixed types (string, number, array, boolean) in the same payload', () => {
const eventConfig = {
event: 'UserSignup',
mapping: [
{ sourceKeys: ['properties.total'], destKey: 'order_total', required: true },
{ sourceKeys: ['properties.value'], destKey: 'order_value', required: true },
{ sourceKeys: ['properties.revenue'], destKey: 'order_revenue', required: true },
{ sourceKeys: ['properties.price'], destKey: 'order_price', required: true },
],
};
const message = {
properties: {
total: '100', // String
value: 0, // Number
revenue: [50, 60], // Array
price: true, // Boolean
},
event: 'UserSignup',
};
const result = prepareStandardEventParams(message, eventConfig);
expect(result).toEqual({
order_total: '100',
order_value: 0,
order_revenue: [50, 60],
order_price: true,
});
});

it('should handle undefined and null values appropriately', () => {
const eventConfig = {
event: 'UserSignup',
mapping: [
{ sourceKeys: ['properties.total'], destKey: 'order_total', required: true },
{ sourceKeys: ['properties.optional'], destKey: 'optional_field', required: false },
],
};

const message = {
properties: {
total: 0,
optional: undefined,
},
event: 'UserSignup',
};

const result = prepareStandardEventParams(message, eventConfig);
expect(result).toEqual({
order_total: 0,
});
});

it('should handle negative values correctly', () => {
const eventConfig = {
event: 'Refund',
mapping: [{ sourceKeys: ['properties.amount'], destKey: 'refund_amount', required: true }],
};

const message = {
properties: {
amount: -50,
},
event: 'Refund',
};

const result = prepareStandardEventParams(message, eventConfig);
expect(result).toEqual({
refund_amount: -50,
});
});

it('Should correctly handle a mix of data types (string, number, array, boolean) and metadata', () => {
const eventConfig = {
event: 'UserSignup',
mapping: [
{ sourceKeys: ['properties.total'], destKey: 'order_total', required: true },
{ sourceKeys: ['properties.value'], destKey: 'order_value', required: true },
{ sourceKeys: ['properties.revenue'], destKey: 'order_revenue', required: true },
{ sourceKeys: ['properties.price'], destKey: 'order_price', required: true },
{ sourceKeys: ['properties.isActive'], destKey: 'user_isActive', required: true },
{ sourceKeys: ['properties.items'], destKey: 'order_items', required: false }, // Optional field
],
};

const message = {
properties: {
total: 0, // Number
value: '200', // String
revenue: [300, 400], // Array
price: 12, // Number
isActive: false, // Boolean
items: ['item1', 'item2'], // Array (optional)
},
event: 'UserSignup',
};

const metadata = {
order_total: 0,
order_value: 0,
order_revenue: 0,
order_price: 12,
user_isActive: true,
order_items: ['item3', 'item4'],
};

const result = prepareStandardEventParams(message, eventConfig, metadata);

// Using toEqual to check if the result matches the expected object
expect(result).toEqual({
order_total: 0,
order_value: '200',
order_revenue: [300, 400],
order_price: 12,
user_isActive: false,
order_items: ['item1', 'item2'],
});
});

it('should return null when required parameters are missing', () => {
const eventConfig = {
event: 'Purchase',
mapping: [
{ sourceKeys: ['properties.total'], destKey: 'order_total', required: true },
{ sourceKeys: ['properties.currency'], destKey: 'currency', required: true }
],
};

const message = {
properties: {
// total is missing
currency: 'USD'
},
event: 'Purchase',
};

const result = prepareStandardEventParams(message, eventConfig);
expect(result).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
customParametersExclusion,
rootLevelProductsSupportedEventsList,
} from './config';
import { isBlank, flattenJson } from '../../utils/commonUtils';
import { isBlank, flattenJson, isDefinedAndNotNull } from '../../utils/commonUtils';
import { constructPayload, extractCustomFields } from '../../utils/utils';

const logger = new Logger(DISPLAY_NAME);
Expand Down Expand Up @@ -297,7 +297,7 @@ const prepareStandardEventParams = (message, eventConfig) => {
// Validation for required params
if (Array.isArray(mapping) && mapping.length > 0) {
const hasMissingRequiredValue = mapping.some(mappingItem => {
if (!payload[mappingItem.destKey] && mappingItem.required) {
if (!isDefinedAndNotNull(payload[mappingItem.destKey]) && mappingItem.required) {
logger.error(`Missing required value from ${JSON.stringify(mappingItem.sourceKeys)}`);
return true;
}
Expand Down Expand Up @@ -370,5 +370,6 @@ export {
getCustomParameters,
removeInvalidParams,
prepareParamsAndEventName,
prepareStandardEventParams,
formatAndValidateEventName,
};