diff --git a/src/v0/destinations/topsort/config.js b/src/v0/destinations/topsort/config.js index d8810ebd04..20becd6398 100644 --- a/src/v0/destinations/topsort/config.js +++ b/src/v0/destinations/topsort/config.js @@ -2,13 +2,14 @@ const { getMappingConfig } = require('../../util'); const BASE_URL = 'https://api.topsort.com/v2/events'; -const ConfigCategories = { +const ConfigCategory = { TRACK: { type: 'track', name: 'TopsortTrackConfig', }, PLACEMENT: { name: 'TopsortPlacementConfig' }, ITEM: { name: 'TopsortItemConfig' }, + PURCHASE_ITEM: { name: 'TopSortPurchaseProductConfig' }, }; const ECOMM_EVENTS_WITH_PRODUCT_ARRAY = [ @@ -20,11 +21,11 @@ const ECOMM_EVENTS_WITH_PRODUCT_ARRAY = [ 'Order Cancelled', ]; -const mappingConfig = getMappingConfig(ConfigCategories, __dirname); +const mappingConfig = getMappingConfig(ConfigCategory, __dirname); module.exports = { mappingConfig, - ConfigCategories, + ConfigCategory, BASE_URL, ECOMM_EVENTS_WITH_PRODUCT_ARRAY, }; diff --git a/src/v0/destinations/topsort/data/TopSortPurchaseProductConfig.json b/src/v0/destinations/topsort/data/TopSortPurchaseProductConfig.json index 50b23daab9..5e00704bb1 100644 --- a/src/v0/destinations/topsort/data/TopSortPurchaseProductConfig.json +++ b/src/v0/destinations/topsort/data/TopSortPurchaseProductConfig.json @@ -1,7 +1,7 @@ [ { "destKey": "productId", - "sourceKeys": ["productId", "properties.productId"] + "sourceKeys": ["product_id", "properties.product_id"] }, { "destKey": "unitPrice", diff --git a/src/v0/destinations/topsort/data/TopsortTrackConfig.json b/src/v0/destinations/topsort/data/TopsortTrackConfig.json index e5c31e0cff..f5762751ea 100644 --- a/src/v0/destinations/topsort/data/TopsortTrackConfig.json +++ b/src/v0/destinations/topsort/data/TopsortTrackConfig.json @@ -8,8 +8,8 @@ "required": true }, { - "destKey": "anonymousId", - "sourceKeys": "opaqueUserId", + "destKey": "opaqueUserId", + "sourceKeys": "anonymousId", "required": true }, { diff --git a/src/v0/destinations/topsort/transform.js b/src/v0/destinations/topsort/transform.js index 15f1b3b153..06cc633444 100644 --- a/src/v0/destinations/topsort/transform.js +++ b/src/v0/destinations/topsort/transform.js @@ -3,58 +3,22 @@ const { ConfigurationError, getHashFromArray, } = require('@rudderstack/integrations-lib'); -const { ConfigCategory, mappingConfig, ECOMM_EVENTS_WITH_PRODUCT_ARRAY } = require('./config'); +const { mappingConfig, ECOMM_EVENTS_WITH_PRODUCT_ARRAY, ConfigCategory } = require('./config'); const { constructPayload, simpleProcessRouterDest } = require('../../util'); const { - constructItemPayloads, - createEventData, isProductArrayValid, getMappedEventName, - addFinalPayload, + processImpressionsAndClicksUtility, + processPurchaseEventUtility, } = require('./utils'); -// Function to process events with a product array -const processProductArray = ({ - products, - basePayload, - placementPayload, - topsortEvent, - finalPayloads, -}) => { - const itemPayloads = constructItemPayloads(products, mappingConfig[ConfigCategory.ITEM.name]); - itemPayloads.forEach((itemPayload) => { - const eventData = createEventData(basePayload, placementPayload, itemPayload, topsortEvent); - addFinalPayload(eventData, finalPayloads); - }); -}; - -// Function to process events with a single product or no product data -const processSingleProduct = ({ - basePayload, - placementPayload, - message, - topsortEvent, - finalPayloads, - messageId, -}) => { - const itemPayload = constructPayload(message, mappingConfig[ConfigCategory.ITEM.name]); - const eventData = createEventData(basePayload, placementPayload, itemPayload, topsortEvent); - - // Ensure messageId is used instead of generating a UUID for single product events - eventData.data.id = messageId; - - // Add final payload with appropriate ID and other headers - addFinalPayload(eventData, finalPayloads); -}; - const responseBuilder = (message, { Config }) => { const { topsortEvents } = Config; const { event, properties } = message; - const { products, messageId } = properties; + const { products } = properties; // Parse Topsort event mappings - const parsedTopsortEventMappings = getHashFromArray(topsortEvents); - const mappedEventName = getMappedEventName(parsedTopsortEventMappings, event); + const mappedEventName = getMappedEventName(getHashFromArray(topsortEvents), event); if (!mappedEventName) { throw new InstrumentationError("Event not mapped in 'topsortEvents'. Dropping the event."); @@ -64,32 +28,44 @@ const responseBuilder = (message, { Config }) => { // Construct base and placement payloads const basePayload = constructPayload(message, mappingConfig[ConfigCategory.TRACK.name]); - const placementPayload = constructPayload(message, mappingConfig[ConfigCategory.PLACEMENT.name]); - - // Check if the event involves a product array (using ECOMM_EVENTS_WITH_PRODUCT_ARRAY) - const isProductArrayAvailable = - ECOMM_EVENTS_WITH_PRODUCT_ARRAY.includes(event) && isProductArrayValid(event, properties); - const finalPayloads = []; + const finalPayloads = { + impressions: [], + clicks: [], + purchases: [], + }; const commonArgs = { basePayload, - placementPayload, topsortEventName, finalPayloads, + products, + message, + isProductArrayAvailable: + ECOMM_EVENTS_WITH_PRODUCT_ARRAY.includes(event) && isProductArrayValid(event, properties), }; - if (isProductArrayAvailable) { - processProductArray({ + // Process events based on type and construct payload within each logic block + if (topsortEventName === 'impressions' || topsortEventName === 'clicks') { + const placementPayload = constructPayload( + message, + mappingConfig[ConfigCategory.PLACEMENT.name], + ); + processImpressionsAndClicksUtility.processImpressionsAndClicks({ ...commonArgs, - products, // Directly use destructured products + placementPayload, // Only pass placementPayload for impressions and clicks }); - } else { - processSingleProduct({ - ...commonArgs, + } else if (topsortEventName === 'purchases') { + const purchasePayload = constructPayload( message, - messageId, // Add 'messageId' for single product event + mappingConfig[ConfigCategory.PURCHASE_ITEM.name], + ); + processPurchaseEventUtility.processPurchaseEvent({ + ...commonArgs, + purchasePayload, // Only pass purchasePayload for purchase events }); + } else { + throw new InstrumentationError(`Unknown event type: ${topsortEventName}`); } return finalPayloads; diff --git a/src/v0/destinations/topsort/utils.js b/src/v0/destinations/topsort/utils.js index 1b03b8a044..75f0b69896 100644 --- a/src/v0/destinations/topsort/utils.js +++ b/src/v0/destinations/topsort/utils.js @@ -1,39 +1,41 @@ -const { generateUUID } = require('@rudderstack/integrations-lib'); +const { generateUUID, ConfigurationError } = require('@rudderstack/integrations-lib'); const { constructPayload } = require('../../util'); +const { ConfigCategory, mappingConfig } = require('./config'); // Function to check if a product array is valid const isProductArrayValid = (event, properties) => Array.isArray(properties?.products) && properties?.products.length > 0; // Function to construct item payloads for each product -const constructItemPayloads = (products, mappingConfig) => - products.map((product) => constructPayload(product, mappingConfig)); - -// Function to create a single event data structure -const createEventData = (basePayload, placementPayload, itemPayload, event) => ({ - data: { - ...basePayload, - placement: { - ...placementPayload, - ...itemPayload, - }, - id: generateUUID(), - }, - event, -}); +const constructItemPayloads = (products, mappingConfigs) => + products.map((product) => constructPayload(product, mappingConfigs)); // Function to add the structured event data to the final payloads array const addFinalPayload = (eventData, finalPayloads) => { - finalPayloads.push(JSON.stringify(eventData)); // Only push the eventData as JSON + switch (eventData.event) { + case 'impressions': + finalPayloads.impressions.push(eventData.topsortPayload); + break; + case 'clicks': + finalPayloads.clicks.push(eventData.topsortPayload); + break; + case 'purchases': + finalPayloads.purchases.push(eventData.topsortPayload); + break; + default: + throw new ConfigurationError('Invalid event mapping'); + } }; // Function to retrieve mapped event name from Topsort event mappings. const getMappedEventName = (parsedTopsortEventMappings, event) => { - const mappedEventNames = parsedTopsortEventMappings[event]; + const eventName = event.toLowerCase(); + + const mappedEventNames = parsedTopsortEventMappings[eventName]; // Check if mapping exists if (!mappedEventNames) { - throw new Error(`Event '${event}' not found in Topsort event mappings`); + throw new Error(`Event '${eventName}' not found in Topsort event mappings`); } // If there are multiple mappings, pick the first one or apply your logic @@ -44,10 +46,183 @@ const getMappedEventName = (parsedTopsortEventMappings, event) => { return mappedEventNames; // Return the single mapping if not an array }; +const processImpressionsAndClicksUtility = { + // Create event data object + createEventData(basePayload, placementPayload, itemPayload, event) { + return { + topsortPayload: { + ...basePayload, + placement: { + ...placementPayload, + ...itemPayload, + }, + id: generateUUID(), + }, + event, + }; + }, + + // Process events with a product array + processProductArray({ + products, + basePayload, + placementPayload, + topsortEventName, + finalPayloads, + }) { + const itemPayloads = constructItemPayloads(products, mappingConfig[ConfigCategory.ITEM.name]); + itemPayloads.forEach((itemPayload) => { + const eventData = this.createEventData( + basePayload, + placementPayload, + itemPayload, + topsortEventName, + ); + addFinalPayload(eventData, finalPayloads); + }); + }, + + // Process events with a single product + processSingleProduct({ + basePayload, + placementPayload, + message, + topsortEventName, + finalPayloads, + }) { + const itemPayload = constructPayload(message, mappingConfig[ConfigCategory.ITEM.name]); + const eventData = this.createEventData( + basePayload, + placementPayload, + itemPayload, + topsortEventName, + ); + + // Ensure messageId is used instead of generating a UUID for single product events + eventData.topsortPayload.id = message.messageId; + + // Add final payload with appropriate ID and other headers + addFinalPayload(eventData, finalPayloads); + }, + + processImpressionsAndClicks({ + isProductArrayAvailable, + basePayload, + topsortEventName, + finalPayloads, + products, + message, + placementPayload, + }) { + if (isProductArrayAvailable) { + // If product array is available, process the event with multiple products + this.processProductArray({ + basePayload, + topsortEventName, + finalPayloads, + products, + placementPayload, + }); + } else { + // Otherwise, process the event with a single product + this.processSingleProduct({ + basePayload, + topsortEventName, + finalPayloads, + message, + placementPayload, + }); + } + }, +}; + +const processPurchaseEventUtility = { + // Create event data object for purchase events + createEventData(basePayload, purchasePayload, itemPayload, event) { + return { + topsortPayload: { + ...basePayload, + items: { + ...purchasePayload, + ...itemPayload, + }, + id: generateUUID(), + }, + event, + }; + }, + + // Function to process events with a product array for purchase events + processProductArray({ products, basePayload, purchasePayload, topsortEventName, finalPayloads }) { + const itemPayloads = constructItemPayloads( + products, + mappingConfig[ConfigCategory.PURCHASE_ITEM.name], + ); + itemPayloads.forEach((itemPayload) => { + const eventData = this.createEventData( + basePayload, + purchasePayload, + itemPayload, + topsortEventName, + ); + addFinalPayload(eventData, finalPayloads); + }); + }, + + // Function to process events with a single product for purchase events + processSingleProduct({ basePayload, purchasePayload, message, topsortEventName, finalPayloads }) { + const itemPayload = constructPayload(message, mappingConfig[ConfigCategory.PURCHASE_ITEM.name]); + const eventData = this.createEventData( + basePayload, + purchasePayload, + itemPayload, + topsortEventName, + ); + + // Ensure messageId is used instead of generating a UUID for single product events + eventData.topsortPayload.id = message.messageId; + + // Add final payload with appropriate ID and other headers + addFinalPayload(eventData, finalPayloads); + }, + + // Function to process purchase events (either with a product array or single product) + processPurchaseEvent({ + isProductArrayAvailable, + basePayload, + topsortEventName, + finalPayloads, + products, + message, + purchasePayload, + }) { + if (isProductArrayAvailable) { + // If product array is available, process the purchase event with multiple products + this.processProductArray({ + basePayload, + topsortEventName, + finalPayloads, + products, + purchasePayload, + }); + } else { + // Otherwise, process the purchase event with a single product + this.processSingleProduct({ + basePayload, + topsortEventName, + finalPayloads, + message, + purchasePayload, + }); + } + }, +}; + module.exports = { isProductArrayValid, constructItemPayloads, - createEventData, addFinalPayload, getMappedEventName, + processImpressionsAndClicksUtility, + processPurchaseEventUtility, };