Skip to content

Commit

Permalink
feat: adding product level tracking for awin
Browse files Browse the repository at this point in the history
  • Loading branch information
shrouti1507 committed Apr 3, 2024
1 parent a2fd622 commit 92ed3ff
Show file tree
Hide file tree
Showing 4 changed files with 538 additions and 3 deletions.
14 changes: 11 additions & 3 deletions src/v0/destinations/awin/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ const { InstrumentationError, ConfigurationError } = require('@rudderstack/integ
const { BASE_URL, ConfigCategory, mappingConfig } = require('./config');
const { defaultRequestConfig, constructPayload, simpleProcessRouterDest } = require('../../util');

const { getParams } = require('./utils');
const { getParams, trackProduct } = require('./utils');

const responseBuilder = (message, { Config }) => {
const { advertiserId, eventsToTrack } = Config;
const { event, properties } = message;
let finalParams = {};

const payload = constructPayload(message, mappingConfig[ConfigCategory.TRACK.name]);

Expand All @@ -17,8 +19,14 @@ const responseBuilder = (message, { Config }) => {
});

// if the event is present in eventsList
if (eventsList.includes(message.event)) {
if (eventsList.includes(event)) {
params = getParams(payload.params, advertiserId);
const productTrackObject = trackProduct(properties, advertiserId, params.parts);

finalParams = {
...params,
...productTrackObject,
};
} else {
throw new InstrumentationError(
"Event is not present in 'Events to Track' list. Aborting message.",
Expand All @@ -27,7 +35,7 @@ const responseBuilder = (message, { Config }) => {
}
}
const response = defaultRequestConfig();
response.params = params;
response.params = finalParams;
response.endpoint = BASE_URL;

return response;
Expand Down
47 changes: 47 additions & 0 deletions src/v0/destinations/awin/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const lodash = require('lodash');

/**
* Returns final params
* @param {*} params
Expand All @@ -24,6 +26,51 @@ const getParams = (parameters, advertiserId) => {
return params;
};

const areAllValuesDefined = (obj) =>
lodash.every(lodash.values(obj), (value) => !lodash.isUndefined(value));

const buildProductPayloadString = (payload) => {
// URL-encode each value, and join back with the same key.
const encodedPayload = Object.entries(payload).reduce((acc, [key, value]) => {
// Encode each value. Assuming that all values are either strings or can be
// safely converted to strings.
acc[key] = encodeURIComponent(value);
return acc;
}, {});

return `AW:P|${encodedPayload.advertiserId}|${encodedPayload.orderReference}|${encodedPayload.productId}|${encodedPayload.productName}|${encodedPayload.productItemPrice}|${encodedPayload.productQuantity}|${encodedPayload.productSku}|${encodedPayload.commissionGroupCode}|${encodedPayload.productCategory}`;
};

// ref: https://wiki.awin.com/index.php/Advertiser_Tracking_Guide/Product_Level_Tracking#PLT_Via_Conversion_Pixel
const trackProduct = (properties, advertiserId, commissionParts) => {
const transformedProductInfoObj = {};
if (properties?.products && properties.products.length > 0) {
const productsArray = properties.products;
let productIndex = 0;
productsArray.forEach((product) => {
const productPayloadNew = {
advertiserId,
orderReference: properties.order_id || properties.orderId,
productId: product.product_id || product.productId,
productName: product.name,
productItemPrice: product.price,
productQuantity: product.quantity,
productSku: product.sku || '',
commissionGroupCode: commissionParts || 'DEFAULT',
productCategory: product.category || '',
};
if (areAllValuesDefined(productPayloadNew)) {
transformedProductInfoObj[`bd[${productIndex}]`] =
buildProductPayloadString(productPayloadNew);
productIndex += 1;
}
});
}
return transformedProductInfoObj;
};

module.exports = {
getParams,
trackProduct,
buildProductPayloadString,
};
165 changes: 165 additions & 0 deletions src/v0/destinations/awin/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
const { buildProductPayloadString, trackProduct } = require('./utils');

describe('buildProductPayloadString', () => {
// Should correctly build the payload string with all fields provided
it('should correctly build the payload string with all fields provided', () => {
const payload = {
advertiserId: '123',
orderReference: 'order123',
productId: 'prod123',
productName: 'Product 1',
productItemPrice: '10.99',
productQuantity: '2',
productSku: 'sku123',
commissionGroupCode: 'DEFAULT',
productCategory: 'Category 1',
};

const expected = 'AW:P|123|order123|prod123|Product%201|10.99|2|sku123|DEFAULT|Category%201';
const result = buildProductPayloadString(payload);

expect(result).toBe(expected);
});

// Should correctly handle extremely long string values for all fields
it('should correctly handle extremely long string values for all fields', () => {
const payload = {
advertiserId: '123',
orderReference: 'order123',
productId: 'prod123',
productName: 'Product 1'.repeat(100000),
productItemPrice: '10.99'.repeat(100000),
productQuantity: '2'.repeat(100000),
productSku: 'sku123'.repeat(100000),
commissionGroupCode: 'DEFAULT',
productCategory: 'Category 1'.repeat(100000),
};

const expected = `AW:P|123|order123|prod123|${encodeURIComponent('Product 1'.repeat(100000))}|${encodeURIComponent('10.99'.repeat(100000))}|${encodeURIComponent('2'.repeat(100000))}|${encodeURIComponent('sku123'.repeat(100000))}|DEFAULT|${encodeURIComponent('Category 1'.repeat(100000))}`;
const result = buildProductPayloadString(payload);

expect(result).toBe(expected);
});
});

describe('trackProduct', () => {
// Given a valid 'properties' object with a non-empty 'products' array, it should transform each product into a valid payload string and return an object with the transformed products.
it("should transform each product into a valid payload string and return an object with the transformed products when given a valid 'properties' object with a non-empty 'products' array", () => {
// Arrange
const properties = {
products: [
{
product_id: '123',
name: 'Product 1',
price: 10,
quantity: 1,
sku: 'SKU123',
category: 'Category 1',
},
{
product_id: '456',
name: 'Product 2',
price: 20,
quantity: 2,
sku: 'SKU456',
category: 'Category 2',
},
],
order_id: 'order123',
};
const advertiserId = 'advertiser123';
const commissionParts = 'COMMISSION';

// Act
const result = trackProduct(properties, advertiserId, commissionParts);

// Assert
expect(result).toEqual({
'bd[0]': 'AW:P|advertiser123|order123|123|Product%201|10|1|SKU123|COMMISSION|Category%201',
'bd[1]': 'AW:P|advertiser123|order123|456|Product%202|20|2|SKU456|COMMISSION|Category%202',
});
});

// Given an invalid 'properties' object, it should return an empty object.
it("should return an empty object when given an invalid 'properties' object", () => {
// Arrange
const properties = {};
const advertiserId = 'advertiser123';
const commissionParts = 'COMMISSION';

// Act
const result = trackProduct(properties, advertiserId, commissionParts);

// Assert
expect(result).toEqual({});
});

it('should ignore the product which has missing properties', () => {
// Arrange
const properties = {
products: [
{
price: 10,
quantity: 1,
sku: 'SKU123',
category: 'Category 1',
},
{
product_id: '456',
name: 'Product 2',
price: 20,
quantity: 2,
sku: 'SKU456',
category: 'Category 2',
},
],
order_id: 'order123',
};
const advertiserId = 'advertiser123';
const commissionParts = 'COMMISSION';

// Act
const result = trackProduct(properties, advertiserId, commissionParts);

// Assert
expect(result).toEqual({
'bd[0]': 'AW:P|advertiser123|order123|456|Product%202|20|2|SKU456|COMMISSION|Category%202',
});
});

it('category and sku if undefined we put blank', () => {
// Arrange
const properties = {
products: [
{
product_id: '123',
name: 'Product 1',
price: 10,
quantity: 1,
sku: undefined,
category: 'Category 1',
},
{
product_id: '456',
name: 'Product 2',
price: 20,
quantity: 2,
sku: 'SKU456',
category: undefined,
},
],
order_id: 'order123',
};
const advertiserId = 'advertiser123';
const commissionParts = 'COMMISSION';

// Act
const result = trackProduct(properties, advertiserId, commissionParts);

// Assert
expect(result).toEqual({
'bd[0]': 'AW:P|advertiser123|order123|123|Product%201|10|1||COMMISSION|Category%201',
'bd[1]': 'AW:P|advertiser123|order123|456|Product%202|20|2|SKU456|COMMISSION|',
});
});
});
Loading

0 comments on commit 92ed3ff

Please sign in to comment.