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

feat: onboard new destination rakuten #3046

Merged
merged 14 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
34 changes: 34 additions & 0 deletions src/cdk/v2/destinations/rakuten/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const { getMappingConfig } = require('../../../../v0/util');

const ConfigCategories = {
TRACK: {
type: 'track',
name: 'propertiesMapping',
},
};
const mappingConfig = getMappingConfig(ConfigCategories, __dirname);
// Following contains the keys at item level mapping where key can be considered as destkey and value can be considered as sourcekey
const productProperties = {
skulist: 'sku',
qlist: 'quantity',
namelist: 'name',
brandlist: 'brand',
couponlist: 'coupon',
catidlist: 'categoryId',
catlist: 'category',
disamtlist: 'discountAmount',
distypelist: 'discountType',
isclearancelist: 'isClearance',
ismarketplacelist: 'isMarketPlace',
issalelist: 'isSale',
itmstatuslist: 'itmStatus',
marginlist: 'margin',
markdownlist: 'markdown',
shipidlist: 'shipId',
shipbylist: 'shipBy',
taxexemptlist: 'taxExempt',
sequencelist: 'sequence',
};
// list of all properties that are required
const requiredProductProperties = ['skulist', 'qlist', 'namelist'];
module.exports = { ConfigCategories, mappingConfig, productProperties, requiredProductProperties };
101 changes: 101 additions & 0 deletions src/cdk/v2/destinations/rakuten/data/propertiesMapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
[
{
"sourceKeys": "properties.orderId",
yashasvibajpai marked this conversation as resolved.
Show resolved Hide resolved
"required": true,
"destKey": "ord"
},
{
"sourceKeys": ["properties.tr", "properties.ranSiteID"],
"required": true,
"destKey": "tr"
},
{
"sourceKeys": ["properties.land", "properties.landTime"],
"required": true,
"destKey": "land"
},
{
"sourceKeys": ["properties.date", "properties.orderCompletedTime"],
"destKey": "date"
},
{
"sourceKeys": ["properties.altord", "properties.alterOrderId"],
"destKey": "altord"
},
{
"sourceKeys": "properties.currency",
"destKey": "cur"
},
{
"sourceKeys": "properties.creditCardType",
"destKey": "cc"
},
{
"sourceKeys": "properties.commReason",
"destKey": "commreason"
},
{
"sourceKeys": "properties.isComm",
"destKey": "iscomm"
},
{
"sourceKeys": "properties.consumed",
"destKey": "consumed"
},
{
"sourceKeys": "properties.coupon",
"destKey": "coupon"
},
{
"sourceKeys": ["properties.custId", "properties.customerId", "properties.userId"],
anantjain45823 marked this conversation as resolved.
Show resolved Hide resolved
"destKey": "custid"
},
{
"sourceKeys": ["properties.custScore", "properties.customerScore"],
"destKey": "custscore"
},
{
"sourceKeys": ["properties.custStatus", "properties.customerStatus"],
"destKey": "custstatus"
},
{
"sourceKeys": ["properties.dId", "properties.advertisingId"],
"destKey": "did"
},
{
"sourceKeys": ["properties.disamt", "properties.discountAmout"],
"destKey": "disamt"
},
{
"sourceKeys": ["properties.ordStatus", "properties.orderStatus"],
"destKey": "ordstatus"
},
{
"sourceKeys": "properties.segment",
"destKey": "segment"
},
{
"sourceKeys": "properties.shipcountry",
"destKey": "shipcountry"
},
{
"sourceKeys": "properties.shipped",
"destKey": "shipped"
},
{
"sourceKeys": ["properties.sitename", "properties.url", "context.page.url"],
"destKey": "sitename"
},
{
"sourceKeys": "properties.storeId",
"destKey": "storeid"
},
{
"sourceKeys": ["properties.storecat", "properties.storeCategory"],
"destKey": "storecat"
},
{
"sourceKeys": "properties.currency",
"destKey": "cur"
}
]
39 changes: 39 additions & 0 deletions src/cdk/v2/destinations/rakuten/procWorkflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
bindings:
- name: EventType
path: ../../../../constants
- path: ../../bindings/jsontemplate
- name: defaultRequestConfig
path: ../../../../v0/util
- name: removeUndefinedAndNullValues
path: ../../../../v0/util
- path: ./utils

steps:
- name: messageType
template: |
.message.type.toLowerCase();
- name: validateInput
template: |
let messageType = $.outputs.messageType;
$.assert(messageType, "message Type is not present. Aborting");
$.assert(messageType in {{$.EventType.([.TRACK])}}, "message type " + messageType + " is not supported");
$.assertConfig(.destination.Config.mid, "Merchant ID is not present. Aborting");
- name: prepareTrackPayload
condition: $.outputs.messageType === {{$.EventType.TRACK}}
template: |
const properties = $.constructProperties(.message);
const lineItems = $.constructLineItems(.message.properties)
$.context.payload = {...properties,...lineItems,xml:1, mid:.destination.Config.mid}
$.context.payload = $.removeUndefinedAndNullValues($.context.payload);

- name: buildResponse
template: |
const response = $.defaultRequestConfig();
response.params = $.context.payload;
response.method = "GET";
response.endpoint = "https://track.linksynergy.com/ep";
response.headers = {
"accept": "application/json",
"content-type": "application/json"
};
response
70 changes: 70 additions & 0 deletions src/cdk/v2/destinations/rakuten/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const { InstrumentationError } = require('@rudderstack/integrations-lib');
const { isDefinedAndNotNull } = require('rudder-transformer-cdk/build/utils');
const {
mappingConfig,
ConfigCategories,
productProperties,
requiredProductProperties,
} = require('./config');
const { constructPayload } = require('../../../../v0/util');

/**
* This fucntion constructs payloads based upon mappingConfig for Track call type
* @param {*} message
* @returns
*/
const constructProperties = (message) => {
const payload = constructPayload(message, mappingConfig[ConfigCategories.TRACK.name]);
return payload;
};

/**
* This fucntion build the item level list
* @param {*} properties
* @returns
*/
const constructLineItems = (properties) => {
// Validate the existence and non-emptiness of the 'products' array in 'properties'
if (!Array.isArray(properties?.products) || properties.products.length === 0) {
throw new InstrumentationError('Either properties.product is not an array or is empty');
}

const { products } = properties;
const productList = {};

// Iterate over product properties to construct the payload
Object.keys(productProperties).forEach((property) => {
const propertyKey = productProperties[property];

// Extract values for the current property from the 'products' array
const values = products.map((product) =>
isDefinedAndNotNull(product?.[propertyKey]) ? product[propertyKey] : '',
);

// Validate if a required property is missing
if (requiredProductProperties.includes(property) && values.includes('')) {
throw new InstrumentationError(`${propertyKey} is a required field. Aborting`);
}

// Include property in the payload if values are non-empty
if (values.some((element) => element !== '')) {
productList[property] = values.join('|');
}
});

// Map 'amountList' by evaluating 'amount' or deriving it from 'price' and 'quantity'
const amountList = products.map((product) => {
if (!product?.amount && !product?.price) {
throw new InstrumentationError('Either amount or price is required for every product');
}

if (product.price) {
return product.quantity * product.price * 100;
}
return product.amount * 100;
});
productList.amtlist = amountList.join('|');
return productList;
};

module.exports = { constructProperties, constructLineItems };
117 changes: 117 additions & 0 deletions src/cdk/v2/destinations/rakuten/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const { constructLineItems } = require('./utils');
describe('constructLineItems', () => {
it('should return a non-empty object when given a valid properties object with at least one product', () => {
const properties = {
products: [
{
name: 'Product 1',
sku: 'sku_1',
price: 10,
quantity: 2,
amount: 20,
},
],
};
const result = constructLineItems(properties);
const expectedObj = {
namelist: 'Product 1',
skulist: 'sku_1',
qlist: '2',
amtlist: '2000',
};
expect(result).toEqual(expectedObj);
});

it('should include all mapped properties in the returned object when present in at least one product', () => {
const properties = {
products: [
{
name: 'Product 1',
category: 'Category 1',
sku: 'sku_1',
brand: 'Brand 1',
price: 10,
quantity: 2,
amount: 20,
},
],
};

const result = constructLineItems(properties);

const expectedObj = {
namelist: 'Product 1',
catlist: 'Category 1',
skulist: 'sku_1',
brandlist: 'Brand 1',
qlist: '2',
amtlist: '2000',
};
expect(result).toEqual(expectedObj);
});

it('should include amtlist property in the returned object with calculated values', () => {
const properties = {
products: [
{
name: 'Product 1',
sku: 'sku_1',
price: 10,
quantity: 2,
},
{
name: 'Product 2',
sku: 'sku_2',
price: 5,
quantity: 3,
},
],
};

const result = constructLineItems(properties);

expect(result).toHaveProperty('amtlist');
expect(result.amtlist).toBe('2000|1500');
});

it('should throw an InstrumentationError when properties object is missing or has an empty products array', () => {
const properties = {};

expect(() => constructLineItems(properties)).toThrow(
'Either properties.product is not an array or is empty',
);

properties.products = [];

expect(() => constructLineItems(properties)).toThrow(
'Either properties.product is not an array or is empty',
);
});
it('should throw an InstrumentationError when a product is missing quantity property', () => {
const properties = {
products: [
{
name: 'Product 1',
sku: 'sku_1',
amount: '1234',
},
],
};
expect(() => constructLineItems(properties)).toThrow('quantity is a required field. Aborting');
});
it('should throw an InstrumentationError when a product is missing both amount and price properties', () => {
const properties = {
products: [
{
name: 'Product 1',
sku: 'sku_1',
quantity: 2,
},
],
};

expect(() => constructLineItems(properties)).toThrow(
'Either amount or price is required for every product',
);
});
});
Loading
Loading