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 topsort destination #3913

Merged
merged 15 commits into from
Dec 13, 2024
31 changes: 31 additions & 0 deletions src/v0/destinations/topsort/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const { getMappingConfig } = require('../../util');

const BASE_URL = 'https://api.topsort.com/v2/events';

const ConfigCategory = {
TRACK: {
type: 'track',
name: 'TopsortTrackConfig',
},
PLACEMENT: { name: 'TopsortPlacementConfig' },
ITEM: { name: 'TopsortItemConfig' },
PURCHASE_ITEM: { name: 'TopSortPurchaseProductConfig' },
};

const ECOMM_EVENTS_WITH_PRODUCT_ARRAY = [
aanshi07 marked this conversation as resolved.
Show resolved Hide resolved
'Cart Viewed',
'Checkout Started',
'Order Updated',
'Order Completed',
'Order Refunded',
'Order Cancelled',
];

const mappingConfig = getMappingConfig(ConfigCategory, __dirname);

module.exports = {
mappingConfig,
ConfigCategory,
BASE_URL,
ECOMM_EVENTS_WITH_PRODUCT_ARRAY,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[
{
"destKey": "productId",
"sourceKeys": ["product_id", "properties.product_id"]
},
{
"destKey": "unitPrice",
"sourceKeys": ["price", "properties.price"],
"metadata": {
"type": "toNumber"
}
},
{
"destKey": "quantity",
"sourceKeys": ["quantity", "properties.quantity"]
},
{
"destKey": "vendorId",
"sourceKeys": "properties.vendorId"
}
]
12 changes: 12 additions & 0 deletions src/v0/destinations/topsort/data/TopsortItemConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"destKey": "position",
"sourceKeys": ["properties.position", "position"],
"required": false
},
{
"destKey": "productId",
"sourceKeys": ["properties.product_id", "product_id"],
"required": false
}
]
30 changes: 30 additions & 0 deletions src/v0/destinations/topsort/data/TopsortPlacementConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"destKey": "path",
"sourceKeys": "context.page.path",
"required": true
},
{
"destKey": "searchQuery",
"sourceKeys": "properties.query",
"required": false
},
{
"destKey": "page",
"sourceKeys": "properties.pageNumber",
"required": false
},
{
"destKey": "pageSize",
"sourceKeys": "properties.pageSize",
"required": false
},
{
"destKey": "categoryIds",
"sourceKeys": "properties.category_id",
"required": false,
"metadata": {
"toArray": true
}
}
]
30 changes: 30 additions & 0 deletions src/v0/destinations/topsort/data/TopsortTrackConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"destKey": "occurredAt",
"sourceKeys": ["originalTimestamp", "timestamp"],
"metadata": {
"type": "timestamp"
},
"required": true
},
{
"destKey": "opaqueUserId",
"sourceKeys": "anonymousId",
"required": true
},
{
"destKey": "resolvedBidId",
"sourceKeys": "properties.resolvedBidId",
"required": false
},
{
"destKey": "entity",
"sourceKeys": "properties.entity",
"required": false
},
{
"destKey": "additionalAttribution",
"sourceKeys": "properties.additionalAttribution",
"required": false
}
]
103 changes: 103 additions & 0 deletions src/v0/destinations/topsort/transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const {
InstrumentationError,
ConfigurationError,
getHashFromArray,
} = require('@rudderstack/integrations-lib');
const { mappingConfig, ECOMM_EVENTS_WITH_PRODUCT_ARRAY, ConfigCategory } = require('./config');
const { constructPayload, simpleProcessRouterDest } = require('../../util');
const {
isProductArrayValid,
getMappedEventName,
processImpressionsAndClicksUtility,
processPurchaseEventUtility,
} = require('./utils');

const responseBuilder = (message, { Config }) => {
const { topsortEvents } = Config;
const { event, properties } = message;
const { products } = properties;

// Parse Topsort event mappings
const mappedEventName = getMappedEventName(getHashFromArray(topsortEvents), event);

if (!mappedEventName) {
throw new InstrumentationError("Event not mapped in 'topsortEvents'. Dropping the event.");

Check warning on line 24 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L24

Added line #L24 was not covered by tests
}

const topsortEventName = mappedEventName;

// Construct base and placement payloads
const basePayload = constructPayload(message, mappingConfig[ConfigCategory.TRACK.name]);

const finalPayloads = {
impressions: [],
clicks: [],
purchases: [],
};

const commonArgs = {
basePayload,
topsortEventName,
finalPayloads,
products,
message,
isProductArrayAvailable:
ECOMM_EVENTS_WITH_PRODUCT_ARRAY.includes(event) && isProductArrayValid(event, properties),
};

// Process events based on type and construct payload within each logic block
if (topsortEventName === 'impressions' || topsortEventName === 'clicks') {
const placementPayload = constructPayload(

Check warning on line 50 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L50

Added line #L50 was not covered by tests
message,
mappingConfig[ConfigCategory.PLACEMENT.name],
);
processImpressionsAndClicksUtility.processImpressionsAndClicks({

Check warning on line 54 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L54

Added line #L54 was not covered by tests
...commonArgs,
placementPayload, // Only pass placementPayload for impressions and clicks
});
} else if (topsortEventName === 'purchases') {
const purchasePayload = constructPayload(

Check warning on line 59 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L59

Added line #L59 was not covered by tests
message,
mappingConfig[ConfigCategory.PURCHASE_ITEM.name],
);
processPurchaseEventUtility.processPurchaseEvent({

Check warning on line 63 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L63

Added line #L63 was not covered by tests
...commonArgs,
purchasePayload, // Only pass purchasePayload for purchase events
});
} else {
throw new InstrumentationError(`Unknown event type: ${topsortEventName}`);
aanshi07 marked this conversation as resolved.
Show resolved Hide resolved
}

return finalPayloads;

Check warning on line 71 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L71

Added line #L71 was not covered by tests
};

const processEvent = (message, destination) => {
// Check for missing API Key or missing Advertiser ID
if (!destination.Config.apiKey) {
throw new ConfigurationError('API Key is missing. Aborting message.', 400);

Check warning on line 77 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L77

Added line #L77 was not covered by tests
}
if (!message.type) {
throw new InstrumentationError('Message Type is missing. Aborting message.', 400);

Check warning on line 80 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L80

Added line #L80 was not covered by tests
}

const messageType = message.type.toLowerCase();

// Handle 'track' event type
if (messageType !== 'track') {
throw new InstrumentationError('Only "track" events are supported. Dropping event.', 400);

Check warning on line 87 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L87

Added line #L87 was not covered by tests
}

return responseBuilder(message, destination);
};

// Process function that is called per event
const process = (event) => processEvent(event.message, event.destination);

// Router destination handler to process a batch of events
const processRouterDest = async (inputs, reqMetadata) => {
// Process all events through the simpleProcessRouterDest utility
const respList = await simpleProcessRouterDest(inputs, process, reqMetadata);
return respList;

Check warning on line 100 in src/v0/destinations/topsort/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/topsort/transform.js#L99-L100

Added lines #L99 - L100 were not covered by tests
};

module.exports = { process, processRouterDest };
Loading
Loading