diff --git a/src/cdk/v2/destinations/klaviyo_bulk_upload/config.js b/src/cdk/v2/destinations/klaviyo_bulk_upload/config.js new file mode 100644 index 0000000000..43a904499b --- /dev/null +++ b/src/cdk/v2/destinations/klaviyo_bulk_upload/config.js @@ -0,0 +1,5 @@ +const KLAVIYO_BULK_UPLOAD = 'klaviyo_bulk_upload'; + +module.exports = { + KLAVIYO_BULK_UPLOAD, +}; diff --git a/src/cdk/v2/destinations/klaviyo_bulk_upload/procWorkflow.yaml b/src/cdk/v2/destinations/klaviyo_bulk_upload/procWorkflow.yaml new file mode 100644 index 0000000000..44290ff520 --- /dev/null +++ b/src/cdk/v2/destinations/klaviyo_bulk_upload/procWorkflow.yaml @@ -0,0 +1,35 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + - name: defaultRequestConfig + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - path: ./utils + - path: ../../bindings/jsontemplate + exportAll: true + - path: ./config + +steps: + - name: messageType + template: | + .message.type.toLowerCase(); + - name: validateInput + template: | + let messageType = .message.type; + $.assert(messageType, "message Type is not present. Aborting"); + $.assert(.message.type.toLowerCase() ==='identify', "Event type " + .message.type.toLowerCase() + " is not supported. Aborting message."); + $.assertConfig(.destination.Config.privateApiKey, "Private Api Key is not present. Aborting"); + + - name: generatePayload + template: | + const transformedPayload = $.combinePayloads(^.{.message.type === $.EventType.IDENTIFY}[], ^[0].destination) + transformedPayload + + - name: buildResponseForProcessTransformation + description: build response + template: | + const response = $.defaultRequestConfig(); + response.body.JSON = $.outputs.generatePayload + response diff --git a/src/cdk/v2/destinations/klaviyo_bulk_upload/utils.js b/src/cdk/v2/destinations/klaviyo_bulk_upload/utils.js new file mode 100644 index 0000000000..c068c4762c --- /dev/null +++ b/src/cdk/v2/destinations/klaviyo_bulk_upload/utils.js @@ -0,0 +1,145 @@ +/* eslint-disable no-param-reassign */ +const { removeUndefinedValues } = require('../../../../v0/util'); + +const locationAttributes = [ + 'address1', + 'address2', + 'city', + 'country', + 'latitude', + 'longitude', + 'region', + 'zip', + 'ip', +]; + +const standardTraits = [ + 'email', + 'phone_number', + 'external_id', + 'anonymous_id', + '_kx', + 'first_name', + 'last_name', + 'organization', + 'title', + 'image', +]; + +function setStandardAndCustomTraits(traits) { + const standardAttributes = {}; + const customAttributes = {}; + return Object.keys(traits).reduce( + (result, key) => { + if (!locationAttributes.includes(key) && standardTraits.includes(key)) { + result.standardAttributes[key] = traits[key]; + } else if (!locationAttributes.includes(key)) { + result.customAttributes[key] = traits[key]; + } + return result; + }, + { standardAttributes, customAttributes }, + ); +} + +function generateLocationObject({ + traits: { address1, address2, city, country, latitude, longitude, region, zip, ip }, +}) { + const locationObject = { + address1, + address2, + city, + country, + latitude, + longitude, + region, + zip, + ip, + }; + + return removeUndefinedValues(locationObject); +} + +function transformSingleMessage(data, metadata) { + const { context, traits } = data; + const location = generateLocationObject(data); + const { jobId } = metadata; + const { standardAttributes, customAttributes } = setStandardAndCustomTraits(traits); + const transformedSinglePayload = { + type: 'profile', + attributes: { + ...standardAttributes, + location, + properties: customAttributes, + anonymous_id: context.externalId[0].id, + jobIdentifier: `${context.externalId[0].id}:${jobId}`, + }, + }; + if (context.externalId[0].identifierType === 'id') { + transformedSinglePayload.id = context.externalId[0].id || traits.id; + transformedSinglePayload.attributes.anonymous_id = context.externalId[0].id; + } else if (context.externalId[0].identifierType === 'email') { + transformedSinglePayload.attributes.email = context.externalId[0].id; + } + if (Object.keys(transformedSinglePayload.attributes.location).length === 0) { + delete transformedSinglePayload.attributes.location; + } + if (Object.keys(transformedSinglePayload.attributes.properties).length === 0) { + delete transformedSinglePayload.attributes.properties; + } + return removeUndefinedValues(transformedSinglePayload); +} + +function wrapCombinePayloads(transformedInputs, destinationObj) { + if (destinationObj.Config.listId) { + return { + payload: { + data: { + type: 'profile-bulk-import-job', + attributes: { + profiles: { + data: transformedInputs, + }, + }, + relationships: { + lists: { + data: [ + { + type: 'list', + id: destinationObj.Config.listId, + }, + ], + }, + }, + }, + }, + destination: destinationObj, + }; + } + return { + payload: { + data: { + type: 'profile-bulk-import-job', + attributes: { + profiles: { + data: transformedInputs, + }, + }, + }, + }, + destination: destinationObj, + }; +} + +function combinePayloads(inputs) { + const transformedInputs = inputs.map((input) => { + const { message, metadata } = input; + return transformSingleMessage(message, metadata); + }); + const destinationObj = inputs[inputs.length - 1].destination; + + const { payload } = wrapCombinePayloads(transformedInputs, destinationObj); + return { ...payload }; +} + +module.exports = { transformSingleMessage, combinePayloads }; diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index 19136eab59..419c56d2c6 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -166,6 +166,13 @@ const DestCanonicalNames = { ], koala: ['Koala', 'koala', 'KOALA'], bloomreach: ['Bloomreach', 'bloomreach', 'BLOOMREACH'], + KLAVIYO_BULK_UPLOAD: [ + 'klaviyo bulk upload', + 'klaviyo_bulk_upload', + 'klaviyoBulkUpload', + 'Klaviyo Bulk Upload', + 'klaviyobulkupload', + ], emarsys: ['EMARSYS', 'Emarsys', 'emarsys'], }; diff --git a/test/integrations/destinations/klaviyo_bulk_upload/processor/data.ts b/test/integrations/destinations/klaviyo_bulk_upload/processor/data.ts new file mode 100644 index 0000000000..0a97f9035d --- /dev/null +++ b/test/integrations/destinations/klaviyo_bulk_upload/processor/data.ts @@ -0,0 +1,490 @@ +export const data = [ + { + name: 'klaviyo_bulk_upload', + description: 'Successful identify event with location data', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'sources', + context: { + externalId: [ + { + id: 'user1', + identifierType: 'userId', + type: 'KLAVIYO_BULK_UPLOAD-userProfiles', + }, + ], + mappedToDestination: 'true', + sources: { + job_id: '2gif2bMzsX1Nt0rbV1vcbAE3cxC', + job_run_id: 'cp5p5ilq47pqg38v2nfg', + task_run_id: 'cp5p5ilq47pqg38v2ng0', + version: '2051/merge', + }, + }, + traits: { + address1: 'dallas street', + address2: 'oppenheimer market', + city: 'delhi', + email: 'qwe22@mail.com', + first_name: 'Testqwe0022', + last_name: 'user', + country: 'India', + phone_number: '+919902330123', + ip: '213.5.6.41', + }, + type: 'identify', + userId: '1', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + privateApiKey: 'pk_dummy_123', + listId: 'list101', + }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: '', + headers: {}, + params: {}, + body: { + JSON: { + data: { + type: 'profile-bulk-import-job', + attributes: { + profiles: { + data: [ + { + type: 'profile', + attributes: { + email: 'qwe22@mail.com', + first_name: 'Testqwe0022', + last_name: 'user', + phone_number: '+919902330123', + location: { + address1: 'dallas street', + address2: 'oppenheimer market', + city: 'delhi', + country: 'India', + ip: '213.5.6.41', + }, + anonymous_id: 'user1', + jobIdentifier: 'user1:1', + }, + }, + ], + }, + }, + relationships: { + lists: { + data: [ + { + type: 'list', + id: 'list101', + }, + ], + }, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + jobId: 1, + }, + }, + ], + }, + }, + }, + { + name: 'klaviyo_bulk_upload', + description: 'Successful identify event without location data', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'sources', + context: { + externalId: [ + { + id: 'user1', + identifierType: 'userId', + type: 'KLAVIYO_BULK_UPLOAD-userProfiles', + }, + ], + mappedToDestination: 'true', + sources: { + job_id: '2gif2bMzsX1Nt0rbV1vcbAE3cxC', + job_run_id: 'cp5p5ilq47pqg38v2nfg', + task_run_id: 'cp5p5ilq47pqg38v2ng0', + version: '2051/merge', + }, + }, + traits: { + email: 'qwe22@mail.com', + first_name: 'Testqwe0022', + last_name: 'user', + phone_number: '+919902330123', + ip: '213.5.6.41', + }, + type: 'identify', + userId: '1', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + privateApiKey: 'pk_dummy_123', + listId: 'list101', + }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: '', + headers: {}, + params: {}, + body: { + JSON: { + data: { + type: 'profile-bulk-import-job', + attributes: { + profiles: { + data: [ + { + type: 'profile', + attributes: { + email: 'qwe22@mail.com', + first_name: 'Testqwe0022', + last_name: 'user', + phone_number: '+919902330123', + location: { + ip: '213.5.6.41', + }, + anonymous_id: 'user1', + jobIdentifier: 'user1:1', + }, + }, + ], + }, + }, + relationships: { + lists: { + data: [ + { + type: 'list', + id: 'list101', + }, + ], + }, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + jobId: 1, + }, + }, + ], + }, + }, + }, + { + name: 'klaviyo_bulk_upload', + description: 'Successful identify event without listId', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'sources', + context: { + externalId: [ + { + id: 'user1', + identifierType: 'userId', + type: 'KLAVIYO_BULK_UPLOAD-userProfiles', + }, + ], + mappedToDestination: 'true', + sources: { + job_id: '2gif2bMzsX1Nt0rbV1vcbAE3cxC', + job_run_id: 'cp5p5ilq47pqg38v2nfg', + task_run_id: 'cp5p5ilq47pqg38v2ng0', + version: '2051/merge', + }, + }, + traits: { + email: 'qwe22@mail.com', + first_name: 'Testqwe0022', + last_name: 'user', + phone_number: '+919902330123', + ip: '213.5.6.41', + }, + type: 'identify', + userId: '1', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + privateApiKey: 'pk_dummy_123', + }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: '', + headers: {}, + params: {}, + body: { + JSON: { + data: { + type: 'profile-bulk-import-job', + attributes: { + profiles: { + data: [ + { + type: 'profile', + attributes: { + email: 'qwe22@mail.com', + first_name: 'Testqwe0022', + last_name: 'user', + phone_number: '+919902330123', + location: { + ip: '213.5.6.41', + }, + anonymous_id: 'user1', + jobIdentifier: 'user1:1', + }, + }, + ], + }, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + jobId: 1, + }, + }, + ], + }, + }, + }, + { + name: 'klaviyo_bulk_upload', + description: 'Successful identify event with custom properties', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'sources', + context: { + externalId: [ + { + id: 'user1', + identifierType: 'userId', + type: 'KLAVIYO_BULK_UPLOAD-userProfiles', + }, + ], + mappedToDestination: 'true', + sources: { + job_id: '2gif2bMzsX1Nt0rbV1vcbAE3cxC', + job_run_id: 'cp5p5ilq47pqg38v2nfg', + task_run_id: 'cp5p5ilq47pqg38v2ng0', + version: '2051/merge', + }, + }, + traits: { + email: 'qwe22@mail.com', + first_name: 'Testqwe0022', + last_name: 'user', + phone_number: '+919902330123', + ip: '213.5.6.41', + last_visit_date: '2020-10-01T00:00:00Z', + lastVisitService: ['Brazilian'], + }, + type: 'identify', + userId: '1', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + privateApiKey: 'pk_dummy_123', + }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: '', + headers: {}, + params: {}, + body: { + JSON: { + data: { + type: 'profile-bulk-import-job', + attributes: { + profiles: { + data: [ + { + type: 'profile', + attributes: { + email: 'qwe22@mail.com', + first_name: 'Testqwe0022', + last_name: 'user', + phone_number: '+919902330123', + location: { + ip: '213.5.6.41', + }, + anonymous_id: 'user1', + jobIdentifier: 'user1:1', + properties: { + lastVisitService: ['Brazilian'], + last_visit_date: '2020-10-01T00:00:00Z', + }, + }, + }, + ], + }, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + jobId: 1, + }, + }, + ], + }, + }, + }, +];