diff --git a/src/v0/sources/adjust/config.ts b/src/v0/sources/adjust/config.ts new file mode 100644 index 0000000000..d1c6ab8242 --- /dev/null +++ b/src/v0/sources/adjust/config.ts @@ -0,0 +1,16 @@ +export const excludedFieldList = [ + 'activity_kind', + 'event', + 'event_name', + 'gps_adid', + 'idfa', + 'idfv', + 'adid', + 'tracker', + 'tracker_name', + 'app_name', + 'ip_address', + 'tracking_enabled', + 'tracker_token', + 'created_at', +]; diff --git a/src/v0/sources/adjust/mapping.json b/src/v0/sources/adjust/mapping.json new file mode 100644 index 0000000000..60ea66281e --- /dev/null +++ b/src/v0/sources/adjust/mapping.json @@ -0,0 +1,52 @@ +[ + { + "sourceKeys": "activity_kind", + "destKeys": "properties.activity_kind" + }, + { + "sourceKeys": "event", + "destKeys": "properties.event_token" + }, + { + "sourceKeys": "event_name", + "destKeys": "event" + }, + { + "sourceKeys": "gps_adid", + "destKeys": "properties.gps_adid" + }, + { + "sourceKeys": "idfa", + "destKeys": "context.device.advertisingId" + }, + { + "sourceKeys": "idfv", + "destKeys": "context.device.id" + }, + { + "sourceKeys": "adid", + "destKeys": "context.device.id " + }, + { + "sourceKeys": "tracker", + "destKeys": "properties.tracker" + }, + { + "sourceKeys": "tracker_name", + "destKeys": "properties.tracker_name" + }, + { "sourceKeys": "tracker_token", "destKeys": "properties.tracker_token" }, + + { + "sourceKeys": "app_name", + "destKeys": "context.app.name" + }, + { + "sourceKeys": "ip_address", + "destKeys": ["context.ip", "request_ip"] + }, + { + "sourceKeys": "tracking_enabled", + "destKeys": "properties.tracking_enabled" + } +] diff --git a/src/v0/sources/adjust/transform.js b/src/v0/sources/adjust/transform.js new file mode 100644 index 0000000000..8568622aeb --- /dev/null +++ b/src/v0/sources/adjust/transform.js @@ -0,0 +1,61 @@ +const lodash = require('lodash'); +const path = require('path'); +const fs = require('fs'); +const { TransformationError, structuredLogger: logger } = require('@rudderstack/integrations-lib'); +const Message = require('../message'); +const { CommonUtils } = require('../../../util/common'); +const { excludedFieldList } = require('./config'); +const { extractCustomFields, generateUUID } = require('../../util'); + +// ref : https://help.adjust.com/en/article/global-callbacks#general-recommended-placeholders +// import mapping json using JSON.parse to preserve object key order +const mapping = JSON.parse(fs.readFileSync(path.resolve(__dirname, './mapping.json'), 'utf-8')); + +const formatProperties = (input) => { + const { query_parameters: qParams } = input; + logger.debug(`[Adjust] Input event: query_params: ${JSON.stringify(qParams)}`); + if (!qParams) { + throw new TransformationError('Query_parameters is missing'); + } + const formattedOutput = {}; + Object.entries(qParams).forEach(([key, [value]]) => { + formattedOutput[key] = value; + }); + return formattedOutput; +}; + +const processEvent = (inputEvent) => { + const message = new Message(`Adjust`); + const event = lodash.cloneDeep(inputEvent); + const formattedPayload = formatProperties(event); + // event type is always track + const eventType = 'track'; + message.setEventType(eventType); + message.setPropertiesV2(formattedPayload, mapping); + let customProperties = {}; + customProperties = extractCustomFields( + formattedPayload, + customProperties, + 'root', + excludedFieldList, + ); + message.properties = { ...message.properties, ...customProperties }; + + if (formattedPayload.created_at) { + const ts = new Date(formattedPayload.created_at * 1000).toISOString(); + message.setProperty('originalTimestamp', ts); + message.setProperty('timestamp', ts); + } + + // adjust does not has the concept of user but we need to set some random anonymousId in order to make the server accept the message + message.anonymousId = generateUUID(); + return message; +}; + +// This fucntion just converts the incoming payload to array of already not and sends it to processEvent +const process = (events) => { + const eventsArray = CommonUtils.toArray(events); + return eventsArray.map(processEvent); +}; + +module.exports = { process }; diff --git a/test/integrations/sources/adjust/data.ts b/test/integrations/sources/adjust/data.ts new file mode 100644 index 0000000000..975543fbec --- /dev/null +++ b/test/integrations/sources/adjust/data.ts @@ -0,0 +1,122 @@ +import utils from '../../../../src/v0/util'; + +const defaultMockFns = () => { + jest.spyOn(utils, 'generateUUID').mockReturnValue('97fcd7b2-cc24-47d7-b776-057b7b199513'); +}; + +export const data = [ + { + name: 'adjust', + description: 'Simple track call', + module: 'source', + version: 'v0', + input: { + request: { + body: [ + { + id: 'adjust', + query_parameters: { + gps_adid: ['38400000-8cf0-11bd-b23e-10b96e40000d'], + adid: ['18546f6171f67e29d1cb983322ad1329'], + tracker_token: ['abc'], + custom: ['custom'], + tracker_name: ['dummy'], + created_at: ['1404214665'], + event_name: ['Click'], + }, + updated_at: '2023-02-10T12:16:07.251Z', + created_at: '2023-02-10T12:05:04.402Z', + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + batch: [ + { + context: { + library: { + name: 'unknown', + version: 'unknown', + }, + integration: { + name: 'Adjust', + }, + device: { + 'id ': '18546f6171f67e29d1cb983322ad1329', + }, + }, + integrations: { + Adjust: false, + }, + type: 'track', + event: 'Click', + originalTimestamp: '2014-07-01T11:37:45.000Z', + timestamp: '2014-07-01T11:37:45.000Z', + properties: { + gps_adid: '38400000-8cf0-11bd-b23e-10b96e40000d', + tracker_token: 'abc', + custom: 'custom', + tracker_name: 'dummy', + }, + anonymousId: '97fcd7b2-cc24-47d7-b776-057b7b199513', + }, + ], + }, + }, + ], + }, + }, + mockFns: () => { + defaultMockFns(); + }, + }, + { + name: 'adjust', + description: 'Simple track call with no query parameters', + module: 'source', + version: 'v0', + input: { + request: { + body: [ + { + id: 'adjust', + updated_at: '2023-02-10T12:16:07.251Z', + created_at: '2023-02-10T12:05:04.402Z', + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + error: 'Query_parameters is missing', + statTags: { + destinationId: 'Non determinable', + errorCategory: 'transformation', + implementation: 'native', + module: 'source', + workspaceId: 'Non determinable', + }, + statusCode: 400, + }, + }, + mockFns: () => { + defaultMockFns(); + }, + }, +];