-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: onboard new destination bloomreach
- Loading branch information
1 parent
bdc0f41
commit cf1c3ba
Showing
14 changed files
with
1,142 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { getMappingConfig } from '../../../../v0/util'; | ||
|
||
export const CUSTOMER_COMMAND = 'customers'; | ||
export const CUSTOMER_EVENT_COMMAND = 'customers/events'; | ||
export const MAX_BATCH_SIZE = 50; | ||
export const getBatchEndpoint = (apiBaseUrl: string, projectToken: string): string => | ||
`${apiBaseUrl}/track/v2/projects/${projectToken}/batch`; | ||
|
||
const CONFIG_CATEGORIES = { | ||
CUSTOMER_PROPERTIES_CONFIG: { name: 'BloomreachCustomerPropertiesConfig' }, | ||
}; | ||
const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); | ||
export const EXCLUSION_FIELDS = [ | ||
'email', | ||
'firstName', | ||
'firstname', | ||
'first_name', | ||
'lastName', | ||
'lastname', | ||
'last_name', | ||
'name', | ||
'phone', | ||
'city', | ||
'birthday', | ||
'country', | ||
]; | ||
export const CUSTOMER_PROPERTIES_CONFIG = | ||
MAPPING_CONFIG[CONFIG_CATEGORIES.CUSTOMER_PROPERTIES_CONFIG.name]; |
36 changes: 36 additions & 0 deletions
36
src/cdk/v2/destinations/bloomreach/data/BloomreachCustomerPropertiesConfig.json
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
[ | ||
{ | ||
"destKey": "first_name", | ||
"sourceKeys": "firstName", | ||
"sourceFromGenericMap": true | ||
}, | ||
{ | ||
"destKey": "last_name", | ||
"sourceKeys": "lastName", | ||
"sourceFromGenericMap": true | ||
}, | ||
{ | ||
"destKey": "email", | ||
"sourceKeys": "emailOnly", | ||
"sourceFromGenericMap": true | ||
}, | ||
{ | ||
"destKey": "phone", | ||
"sourceKeys": "phone", | ||
"sourceFromGenericMap": true | ||
}, | ||
{ | ||
"destKey": "city", | ||
"sourceKeys": "city", | ||
"sourceFromGenericMap": true | ||
}, | ||
{ | ||
"destKey": "country", | ||
"sourceKeys": ["traits.address.country", "context.traits.address.country"] | ||
}, | ||
{ | ||
"destKey": "birthday", | ||
"sourceKeys": "birthday", | ||
"sourceFromGenericMap": true | ||
} | ||
] |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
bindings: | ||
- name: EventType | ||
path: ../../../../constants | ||
- path: ../../bindings/jsontemplate | ||
- name: defaultRequestConfig | ||
path: ../../../../v0/util | ||
- name: toUnixTimestamp | ||
path: ../../../../v0/util | ||
- name: base64Convertor | ||
path: ../../../../v0/util | ||
- name: removeUndefinedAndNullValues | ||
path: ../../../../v0/util | ||
- name: generateExclusionList | ||
path: ../../../../v0/util | ||
- name: extractCustomFields | ||
path: ../../../../v0/util | ||
- name: constructPayload | ||
path: ../../../../v0/util | ||
- path: ./utils | ||
- path: ./config | ||
|
||
steps: | ||
- name: messageType | ||
template: | | ||
$.context.messageType = .message.type.toLowerCase(); | ||
- name: validateInput | ||
template: | | ||
let messageType = $.context.messageType; | ||
$.assert(messageType, "message Type is not present. Aborting"); | ||
$.assert(messageType in {{$.EventType.([.IDENTIFY,.TRACK,.PAGE,.SCREEN])}}, "message type " + messageType + " is not supported"); | ||
$.assertConfig(.destination.Config.apiBaseUrl, "API Base URL is not present. Aborting"); | ||
$.assertConfig(.destination.Config.apiKey, "API Key is not present . Aborting"); | ||
$.assertConfig(.destination.Config.apiSecret, "API Secret is not present. Aborting"); | ||
$.assertConfig(.destination.Config.projectToken, "Project Token is not present. Aborting"); | ||
$.assertConfig(.destination.Config.hardID, "Hard ID is not present. Aborting"); | ||
$.assertConfig(.destination.Config.softID, "Soft ID is not present. Aborting"); | ||
$.assert(.message.timestamp ?? .message.originalTimestamp, "Timestamp is not present. Aborting"); | ||
const userId = .message.().( | ||
{{{{$.getGenericPaths("userIdOnly")}}}}; | ||
); | ||
$.assert(userId ?? .message.anonymousId, "Either one of userId or anonymousId is required. Aborting"); | ||
- name: prepareIdentifyPayload | ||
condition: $.context.messageType === {{$.EventType.IDENTIFY}} | ||
template: | | ||
const customerIDs = $.prepareCustomerIDs(.message, .destination); | ||
const customerProperties = $.constructPayload(.message, $.CUSTOMER_PROPERTIES_CONFIG); | ||
const extraCustomerProperties = $.extractCustomFields(.message, {}, ['traits', 'context.traits'], $.EXCLUSION_FIELDS); | ||
const properties = { | ||
...customerProperties, | ||
...extraCustomerProperties | ||
} | ||
const data = .message.().({ | ||
"customer_ids": customerIDs, | ||
"update_timestamp": $.toUnixTimestamp({{{{$.getGenericPaths("timestamp")}}}}), | ||
properties | ||
}); | ||
$.context.payload = $.removeUndefinedAndNullValues({name: $.CUSTOMER_COMMAND, data}) | ||
- name: prepareEventName | ||
steps: | ||
- name: pageEventName | ||
condition: $.context.messageType === {{$.EventType.PAGE}} | ||
template: | | ||
const category = .message.category ?? .message.properties.category; | ||
const name = .message.name || .message.properties.name; | ||
const eventNameArray = ["Viewed"]; | ||
category ? eventNameArray.push(category); | ||
name ? eventNameArray.push(name); | ||
eventNameArray.push("Page"); | ||
$.context.event = eventNameArray.join(" "); | ||
- name: screenEventName | ||
condition: $.context.messageType === {{$.EventType.SCREEN}} | ||
template: | | ||
const category = .message.category ?? .message.properties.category; | ||
const name = .message.name || .message.properties.name; | ||
const eventNameArray = ["Viewed"]; | ||
category ? eventNameArray.push(category); | ||
name ? eventNameArray.push(name); | ||
eventNameArray.push("Screen"); | ||
$.context.event = eventNameArray.join(" "); | ||
- name: trackEventName | ||
condition: $.context.messageType === {{$.EventType.TRACK}} | ||
template: | | ||
$.assert(.message.event, "Event name is required. Aborting"); | ||
$.context.event = .message.event | ||
- name: prepareTrackPageScreenPayload | ||
condition: $.context.messageType !== {{$.EventType.IDENTIFY}} | ||
template: | | ||
const customerIDs = $.prepareCustomerIDs(.message, .destination); | ||
const data = .message.().({ | ||
"customer_ids": customerIDs, | ||
"timestamp": $.toUnixTimestamp({{{{$.getGenericPaths("timestamp")}}}}), | ||
"properties": .properties, | ||
"event_type": $.context.event, | ||
}); | ||
$.context.payload = $.removeUndefinedAndNullValues({name: $.CUSTOMER_EVENT_COMMAND, data}) | ||
- name: buildResponse | ||
description: In batchMode we return payload directly | ||
condition: $.batchMode | ||
template: | | ||
$.context.payload | ||
else: | ||
name: buildResponseForProcessTransformation | ||
template: | | ||
const response = $.defaultRequestConfig(); | ||
response.body.JSON = $.context.payload; | ||
response.endpoint = $.getBatchEndpoint(.destination.Config.apiBaseUrl, .destination.Config.projectToken); | ||
response.method = "POST"; | ||
response.headers = { | ||
"Content-Type": "application/json", | ||
"Authorization": "Basic " + $.base64Convertor(.destination.Config.apiKey + ":" + .destination.Config.apiSecret) | ||
} | ||
response; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
bindings: | ||
- name: handleRtTfSingleEventError | ||
path: ../../../../v0/util/index | ||
- path: ./utils | ||
exportAll: true | ||
- name: base64Convertor | ||
path: ../../../../v0/util | ||
- name: toUnixTimestamp | ||
path: ../../../../v0/util | ||
- name: BatchUtils | ||
path: '@rudderstack/workflow-engine' | ||
- path: ./config | ||
|
||
steps: | ||
- name: validateInput | ||
template: | | ||
$.assert(Array.isArray(^) && ^.length > 0, "Invalid event array") | ||
- name: transform | ||
externalWorkflow: | ||
path: ./procWorkflow.yaml | ||
bindings: | ||
- name: batchMode | ||
value: true | ||
loopOverInput: true | ||
|
||
- name: successfulEvents | ||
template: | | ||
$.outputs.transform#idx.output.({ | ||
"batchedRequest": ., | ||
"batched": false, | ||
"destination": ^[idx].destination, | ||
"metadata": ^[idx].metadata, | ||
"statusCode": 200 | ||
})[] | ||
- name: failedEvents | ||
template: | | ||
$.outputs.transform#idx.error.( | ||
$.handleRtTfSingleEventError(^[idx], .originalError ?? ., {}) | ||
)[] | ||
- name: batchSuccessfulEvents | ||
description: Batches the successfulEvents | ||
template: | | ||
let batches = $.BatchUtils.chunkArrayBySizeAndLength( | ||
$.outputs.successfulEvents, {maxItems: $.MAX_BATCH_SIZE}).items; | ||
batches@batch.({ | ||
"batchedRequest": { | ||
"body": { | ||
"JSON": {"commands": ~r batch.batchedRequest[]}, | ||
"JSON_ARRAY": {}, | ||
"XML": {}, | ||
"FORM": {} | ||
}, | ||
"version": "1", | ||
"type": "REST", | ||
"method": "POST", | ||
"endpoint": batch[0].destination.Config.().($.getBatchEndpoint(.apiBaseUrl, .projectToken)), | ||
"headers": batch[0].destination.Config.().({ | ||
"Content-Type": "application/json", | ||
"Authorization": "Basic " + $.base64Convertor(.apiKey + ":" + .apiSecret) | ||
}), | ||
"params": {}, | ||
"files": {} | ||
}, | ||
"metadata": ~r batch.metadata[], | ||
"batched": true, | ||
"statusCode": 200, | ||
"destination": batch[0].destination | ||
})[]; | ||
- name: finalPayload | ||
template: | | ||
[...$.outputs.batchSuccessfulEvents, ...$.outputs.failedEvents] |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { isObject, isEmptyObject, getIntegrationsObj } from '../../../../v0/util'; | ||
|
||
const getCustomerIDsFromIntegrationObject = (message) => { | ||
const integrationObj = getIntegrationsObj(message, 'bloomreach' as any) || {}; | ||
const { hardID, softID } = integrationObj; | ||
const customerIDs = {}; | ||
|
||
if (isObject(hardID) && !isEmptyObject(hardID)) { | ||
Object.keys(hardID).forEach((id) => { | ||
customerIDs[id] = hardID[id]; | ||
}); | ||
} | ||
|
||
if (isObject(softID) && !isEmptyObject(softID)) { | ||
Object.keys(softID).forEach((id) => { | ||
customerIDs[id] = softID[id]; | ||
}); | ||
} | ||
|
||
return customerIDs; | ||
}; | ||
|
||
export const prepareCustomerIDs = (message, destination) => { | ||
const customerIDs = { | ||
[destination.Config.hardID]: message.userId, | ||
[destination.Config.softID]: message.anonymousId, | ||
...getCustomerIDsFromIntegrationObject(message), | ||
}; | ||
return customerIDs; | ||
}; |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { Destination } from '../../../../src/types'; | ||
|
||
const destType = 'bloomreach'; | ||
const destTypeInUpperCase = 'BLOOMREACH'; | ||
const displayName = 'bloomreach'; | ||
const channel = 'web'; | ||
const destination: Destination = { | ||
Config: { | ||
apiBaseUrl: 'https://demoapp-api.bloomreach.com', | ||
apiKey: 'test-api-key', | ||
apiSecret: 'test-api-secret', | ||
projectToken: 'test-project-token', | ||
hardID: 'registered', | ||
softID: 'cookie', | ||
}, | ||
DestinationDefinition: { | ||
DisplayName: displayName, | ||
ID: '123', | ||
Name: destTypeInUpperCase, | ||
Config: { cdkV2Enabled: true }, | ||
}, | ||
Enabled: true, | ||
ID: '123', | ||
Name: destTypeInUpperCase, | ||
Transformations: [], | ||
WorkspaceID: 'test-workspace-id', | ||
}; | ||
|
||
const traits = { | ||
email: '[email protected]', | ||
firstName: 'John', | ||
lastName: 'Doe', | ||
phone: '1234567890', | ||
address: { | ||
city: 'New York', | ||
country: 'USA', | ||
pinCode: '123456', | ||
}, | ||
}; | ||
|
||
const properties = { | ||
product_id: '622c6f5d5cf86a4c77358033', | ||
sku: '8472-998-0112', | ||
category: 'Games', | ||
name: 'Cones of Dunshire', | ||
brand: 'Wyatt Games', | ||
variant: 'expansion pack', | ||
price: 49.99, | ||
quantity: 5, | ||
coupon: 'PREORDER15', | ||
currency: 'USD', | ||
position: 1, | ||
url: 'https://www.website.com/product/path', | ||
image_url: 'https://www.website.com/product/path.webp', | ||
key1: 'value1', | ||
}; | ||
const endpoint = 'https://demoapp-api.bloomreach.com/track/v2/projects/test-project-token/batch'; | ||
|
||
const processorInstrumentationErrorStatTags = { | ||
destType: destTypeInUpperCase, | ||
errorCategory: 'dataValidation', | ||
errorType: 'instrumentation', | ||
feature: 'processor', | ||
implementation: 'cdkV2', | ||
module: 'destination', | ||
destinationId: 'default-destinationId', | ||
workspaceId: 'default-workspaceId', | ||
}; | ||
|
||
const RouterInstrumentationErrorStatTags = { | ||
...processorInstrumentationErrorStatTags, | ||
feature: 'router', | ||
}; | ||
|
||
const headers = { | ||
'Content-Type': 'application/json', | ||
Authorization: 'Basic dGVzdC1hcGkta2V5OnRlc3QtYXBpLXNlY3JldA==', | ||
}; | ||
|
||
export { | ||
destType, | ||
channel, | ||
destination, | ||
processorInstrumentationErrorStatTags, | ||
RouterInstrumentationErrorStatTags, | ||
traits, | ||
headers, | ||
properties, | ||
endpoint, | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import * as config from '../../../../src/cdk/v2/destinations/bloomreach/config'; | ||
|
||
export const defaultMockFns = () => { | ||
jest.replaceProperty(config, 'MAX_BATCH_SIZE', 3 as any); | ||
}; |
Oops, something went wrong.