Last active
October 31, 2024 18:53
-
-
Save simone-coelho/1e8fb026a4ca4f1f0d3ed7c37a4d4ec4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const axios = require('axios'); | |
| /** | |
| * Maps mParticle event data to Optimizely's Full Stack event format | |
| * @param {MParticleEvent} mpEvent - The mParticle event object | |
| * @param {Array} eventMap - List of events with their corresponding IDs | |
| * @returns {OptimizelyEvent} Formatted Optimizely event payload | |
| */ | |
| function createOptimizelyPayload(mpEvent, eventMap) { | |
| if (!mpEvent?.data?.custom_event_type) { | |
| throw new Error('Invalid mParticle event data: custom_event_type is required'); | |
| } | |
| // Retrieve visitor ID (fallback to UUID if no customer_id or email is available) | |
| const visitorId = mpEvent.user_identities?.customer_id || | |
| mpEvent.user_identities?.email || | |
| generateUUID(); | |
| // Find corresponding Full Stack event ID for the custom event type | |
| const entityId = findEntityId(mpEvent.data.custom_event_type, eventMap); | |
| if (!entityId) { | |
| throw new Error(`Event ${mpEvent.data.custom_event_type} does not exist in Full Stack event mapping`); | |
| } | |
| // Build visitor attributes, including bot filtering | |
| const attributes = buildVisitorAttributes(mpEvent.data.custom_attributes || {}); | |
| return { | |
| account_id: process.env.OPTIMIZELY_ACCOUNT_ID, | |
| project_id: process.env.OPTIMIZELY_PROJECT_ID, | |
| revision: "100", // revision of the datafile, can be made dynamic | |
| visitors: [{ | |
| visitor_id: visitorId, | |
| attributes, | |
| snapshots: [{ | |
| decisions: [], | |
| events: [{ | |
| entity_id: entityId, | |
| key: mpEvent.data.custom_event_type.replace(/ /g, '_'), | |
| timestamp: Date.now(), | |
| uuid: generateUUID(), | |
| revenue: mpEvent.data.custom_attributes?.revenue || 0, | |
| value: mpEvent.data.custom_attributes?.value || 0 | |
| }] | |
| }] | |
| }], | |
| anonymize_ip: true, | |
| client_name: "Optimizely/fswebintegration", | |
| client_version: "1.0.0", | |
| enrich_decisions: true | |
| }; | |
| } | |
| /** | |
| * Finds the entity ID for an event in the Full Stack datafile mapping | |
| * @param {string} eventName - The event name from mParticle | |
| * @param {Array} eventMap - Event mapping list from Full Stack datafile | |
| * @returns {string|null} The entity ID of the event or null if not found | |
| */ | |
| function findEntityId(eventName, eventMap) { | |
| const event = eventMap.find(e => e.key === eventName); | |
| return event ? event.id : null; | |
| } | |
| /** | |
| * Builds visitor attributes array, adding bot filtering if not present | |
| * @param {Object} customAttributes - Custom attributes from mParticle event | |
| * @returns {Array<Object>} Array of attribute objects for the visitor | |
| */ | |
| function buildVisitorAttributes(customAttributes) { | |
| const attributes = Object.entries(customAttributes).map(([key, value]) => ({ | |
| entity_id: null, | |
| key, | |
| type: "custom", | |
| value | |
| })); | |
| // Add bot filtering attribute | |
| attributes.push({ | |
| entity_id: "$opt_bot_filtering", | |
| key: "$opt_bot_filtering", | |
| type: "custom", | |
| value: false | |
| }); | |
| return attributes; | |
| } | |
| /** | |
| * Generates a UUID v4 | |
| * @returns {string} UUID string | |
| */ | |
| function generateUUID() { | |
| return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | |
| const r = Math.random() * 16 | 0; | |
| const v = c === 'x' ? r : (r & 0x3 | 0x8); | |
| return v.toString(16); | |
| }); | |
| } | |
| /** | |
| * Sends event data to Optimizely's LogX endpoint | |
| * @param {OptimizelyEvent} payload - The Optimizely event payload | |
| * @returns {Promise<Object>} Response from Optimizely API | |
| */ | |
| async function sendToOptimizely(payload) { | |
| try { | |
| const response = await axios.post('https://logx.optimizely.com/v1/events', payload, { | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| } | |
| }); | |
| return response.data; | |
| } catch (error) { | |
| console.error('Error sending event to Optimizely:', error.message); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Main Lambda handler function | |
| * @param {Object} event - AWS Lambda event object containing mParticle data | |
| * @param {Object} context - AWS Lambda context | |
| * @returns {Promise<Object>} Lambda response | |
| */ | |
| exports.handler = async (event, context) => { | |
| try { | |
| // Validate required environment variables | |
| if (!process.env.OPTIMIZELY_PROJECT_ID || !process.env.OPTIMIZELY_ACCOUNT_ID) { | |
| throw new Error('OPTIMIZELY_PROJECT_ID and OPTIMIZELY_ACCOUNT_ID environment variables are required'); | |
| } | |
| // Parse and validate mParticle event data | |
| const mpEvent = JSON.parse(event.body); | |
| if (mpEvent.event_type !== 'custom_event') { | |
| return { | |
| statusCode: 200, | |
| body: JSON.stringify({ message: 'Event type not tracked' }) | |
| }; | |
| } | |
| // Fetch the Full Stack event mapping datafile if needed | |
| const eventMap = await fetchEventMapping(); | |
| // Create Optimizely payload | |
| let optimizelyPayload; | |
| try { | |
| optimizelyPayload = createOptimizelyPayload(mpEvent, eventMap); | |
| } catch (err) { | |
| console.error('Error creating Optimizely payload:', err.message); | |
| throw new Error('Failed to create Optimizely payload'); | |
| } | |
| // Send event to Optimizely | |
| await sendToOptimizely(optimizelyPayload); | |
| return { | |
| statusCode: 200, | |
| body: JSON.stringify({ | |
| message: 'Event successfully forwarded to Optimizely', | |
| eventName: optimizelyPayload.visitors[0].snapshots[0].events[0].key | |
| }) | |
| }; | |
| } catch (error) { | |
| console.error('Error processing event:', error.message); | |
| return { | |
| statusCode: 500, | |
| body: JSON.stringify({ | |
| message: 'Error processing event', | |
| error: error.message | |
| }) | |
| }; | |
| } | |
| }; | |
| /** | |
| * This is an example of how you can support the event mapping between Optimizely and Marticle | |
| * This would require making an additional HTTP request. A better option is to provide a cached | |
| * dictionary, or embed it into the rule's code itself to avoide adding latency from a request. | |
| * | |
| * Fetches the Optimizely datafile and parses event mappings. | |
| * @returns {Array} Array of events with id and key properties | |
| */ | |
| async function fetchEventMapping() { | |
| const lastFetch = localStorage.getItem('OptlyDatafileFetch') || 0; | |
| const needsUpdate = Date.now() - lastFetch > 86400 * 1000; // Update every 24 hours | |
| if (needsUpdate) { | |
| const response = await axios.get(`https://cdn.optimizely.com/datafiles/${process.env.OPTIMIZELY_ENV_KEY}.json`); | |
| const datafile = response.data; | |
| localStorage.setItem('OptlyDatafile', JSON.stringify(datafile)); | |
| localStorage.setItem('OptlyDatafileFetch', Date.now()); | |
| } | |
| const datafile = JSON.parse(localStorage.getItem('OptlyDatafile') || '{}'); | |
| return datafile.events?.map(event => ({ id: event.id, key: event.key })) || []; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment