From da0ffb8c2f68ee1927bdca8c911e15086624eb61 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Wed, 1 Nov 2023 19:51:26 +0530 Subject: [PATCH 01/15] feat: onboard gladly destination --- .../v2/destinations/gladly/procWorkflow.yaml | 93 +++++++++++++++++++ .../v2/destinations/gladly/rtWorkflow.yaml | 31 +++++++ src/cdk/v2/destinations/gladly/utils.js | 74 +++++++++++++++ src/constants/destinationCanonicalNames.js | 1 + src/features.json | 3 +- 5 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 src/cdk/v2/destinations/gladly/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/gladly/rtWorkflow.yaml create mode 100644 src/cdk/v2/destinations/gladly/utils.js diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml new file mode 100644 index 0000000000..0c6813227c --- /dev/null +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -0,0 +1,93 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ./utils + exportAll: true + - name: isDefinedAndNotNull + path: ../../../../v0/util + - name: defaultRequestConfig + path: ../../../../v0/util + - name: getIntegrationsObj + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - name: handleHttpRequest + path: ../../../../adapters/network + +steps: + - name: checkIfProcessed + condition: .message.statusCode + template: | + $.batchMode ? .message.body.JSON : .message + onComplete: return + + - name: messageType + template: | + .message.type.toLowerCase() + + - name: validateInput + template: | + let messageType = $.outputs.messageType + $.assert(messageType, "message Type is not present. Aborting.") + $.assert(messageType in {{$.EventType.([.IDENTIFY])}}, "message type " + messageType + " is not supported") + $.assertConfig(.destination.Config.apiToken, "API Token is not present. Aborting") + $.assertConfig(.destination.Config.domain, "Gladly domain is not present. Aborting") + $.assertConfig(.destination.Config.userName, "User Name is not present. Aborting") + + - name: queryParams + description: Build query params from payload to find user + template: | + const integrationsObj = $.getIntegrationsObj(.message, "gladly"); + $.getQueryParams(.message, integrationsObj) + + - name: findUser + description: Fetch user details if exists + condition: $.isDefinedAndNotNull($.outputs.queryParams) && $.isDefinedAndNotNull($.outputs.queryParams.key) && $.isDefinedAndNotNull($.outputs.queryParams.value) + template: | + const url = $.getEndpoint(.destination) + "?" + $.outputs.queryParams.key + "=" + $.outputs.queryParams.value + const requestOptions = { + headers: $.getHeaders(.destination) + } + const rawResponse = await $.handleHttpRequest("get", url, requestOptions) + const processedResponse = rawResponse.processedResponse + $.assertHttpResp(processedResponse, "Find User Lookup Failed due to " + JSON.stringify(processedResponse.response)) + processedResponse.response + + - name: preparePayload + template: | + $.context.payload = { + name: .message.context.traits.name || .message.traits.name, + image: .message.context.traits.avatar || .message.traits.avatar, + address: .message.context.traits.address || .message.traits.address + } + $.context.payload.emails = $.formatField(.message, "email") + $.context.payload.phones = $.formatField(.message, "phone") + $.context.payload.customAttributes = $.getCustomAttributes(.message) + $.context.payload.externalCustomerId = .message.context.externalId[0].id + $.context.payload = $.removeUndefinedAndNullValues($.context.payload) + + - name: createUser + description: Prepare create user payload + condition: $.outputs.findUser.length === 0 || !$.isDefinedAndNotNull($.outputs.queryParams) + template: | + $.assert(.message.userId, "userId is required") + $.context.payload.id = .message.userId + $.context.method = "POST" + $.context.endpoint = $.getEndpoint(.destination) + + - name: updateUser + description: Prepare update user payload + condition: $.outputs.findUser.length > 0 + template: | + $.context.method = "PATCH" + $.context.endpoint = $.getEndpoint(.destination) + "/" + .message.userId + + - name: buildResponseForProcessTransformation + description: build response + template: | + const response = $.defaultRequestConfig() + response.body.JSON = $.context.payload + response.endpoint = $.context.endpoint + response.method = $.context.method + response.headers = $.getHeaders(.destination) + response; diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml new file mode 100644 index 0000000000..a907403ca4 --- /dev/null +++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml @@ -0,0 +1,31 @@ +bindings: + - name: handleRtTfSingleEventError + path: ../../../../v0/util/index + +steps: + - name: validateInput + template: | + $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array") + + - name: transform + externalWorkflow: + path: ./procWorkflow.yaml + 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: finalPayload + template: | + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js new file mode 100644 index 0000000000..4b2026e106 --- /dev/null +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -0,0 +1,74 @@ +const { getFieldValueFromMessage, base64Convertor, getDestinationExternalID } = require('../../../../v0/util'); + +const lookUpFields = ['email', 'phone', 'externalCustomerId']; +const reservedCustomAttributes = [ + 'email', + 'phone', + 'address', + 'name', + 'avatar', + 'firstName', + 'lstName', + 'userId', +]; + +const getHeaders = (destination) => { + const { apiToken, userName } = destination.Config; + return { + 'Content-Type': 'application/json', + Authorization: `Basic ${base64Convertor(`${userName}:${apiToken}`)}`, + }; +}; + +const getEndpoint = (destination) => { + const { domain } = destination.Config; + return `https://${domain}/api/v1/customer-profiles`; +}; + +const formatField = (message, fieldName) => { + const field = getFieldValueFromMessage(message, fieldName); + if (field) { + if (typeof field === 'object' && Array.isArray(field)) { + return field.map((item) => ({ original: item })); + } + return [{ original: field }]; + } + return undefined; +}; + +const getQueryParams = (message, integrationsObj) => { + let queryParamKey; + let queryParamValue; + if (integrationsObj && integrationsObj.lookup){ + queryParamKey = integrationsObj.lookup; + } + if (!queryParamKey) { + queryParamKey = 'email'; + } + if (!lookUpFields.includes(queryParamKey)) { + queryParamKey = 'email'; + } + + if (queryParamKey === 'email' || queryParamKey === 'phone') { + queryParamValue = getFieldValueFromMessage(message,queryParamKey); + if (!queryParamValue) return undefined; + } else{ + queryParamValue = getDestinationExternalID(message, 'gladlyExternalCustomerId'); + if (!queryParamValue) return undefined; + } + + queryParamKey = queryParamKey === 'phone' ? 'phoneNumber' : queryParamKey; + return { key: queryParamKey, value: queryParamValue }; +}; + +const getCustomAttributes = (message) => { + const customAttributes = message.context.traits; + reservedCustomAttributes.forEach((customAttribute) => { + if (customAttributes[customAttribute]) { + delete customAttributes[customAttribute]; + } + }); + return customAttributes; +}; + +module.exports = { formatField, getCustomAttributes, getEndpoint, getQueryParams, getHeaders }; diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index 870c534db0..9de4640210 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -140,6 +140,7 @@ const DestCanonicalNames = { 'TWITTER_ADS', ], BRAZE: ['BRAZE', 'Braze', 'braze'], + gladly: ['GLADLY', 'gladly', 'Gladly'] }; module.exports = { DestHandlerMap, DestCanonicalNames }; diff --git a/src/features.json b/src/features.json index 7de214ab39..9b984fae84 100644 --- a/src/features.json +++ b/src/features.json @@ -58,6 +58,7 @@ "OPTIMIZELY_FULLSTACK": true, "TWITTER_ADS": true, "CLEVERTAP": true, - "ORTTO": true + "ORTTO": true, + "GLADLY": true } } From 4370cee520d3d18200a006af19b913a6992e94b0 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Thu, 2 Nov 2023 11:10:35 +0530 Subject: [PATCH 02/15] chore: code review changes --- src/cdk/v2/destinations/gladly/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index 4b2026e106..17ee55bdc8 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -28,7 +28,7 @@ const getEndpoint = (destination) => { const formatField = (message, fieldName) => { const field = getFieldValueFromMessage(message, fieldName); if (field) { - if (typeof field === 'object' && Array.isArray(field)) { + if (Array.isArray(field)) { return field.map((item) => ({ original: item })); } return [{ original: field }]; From f82a655356fe1f428c5aa1a403f97be2eeb8f180 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 6 Nov 2023 11:49:19 +0530 Subject: [PATCH 03/15] chore: code review changes --- .../v2/destinations/gladly/procWorkflow.yaml | 63 +++++++------------ src/constants/destinationCanonicalNames.js | 3 +- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index 0c6813227c..5a4d3c337d 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -7,12 +7,15 @@ bindings: path: ../../../../v0/util - name: defaultRequestConfig path: ../../../../v0/util - - name: getIntegrationsObj - path: ../../../../v0/util - name: removeUndefinedAndNullValues path: ../../../../v0/util - - name: handleHttpRequest + - name: getDestinationExternalID + path: ../../../../v0/util + - name: httpPOST path: ../../../../adapters/network + - name: processAxiosResponse + path: ../../../../adapters/utils/networkUtils + steps: - name: checkIfProcessed @@ -33,25 +36,7 @@ steps: $.assertConfig(.destination.Config.apiToken, "API Token is not present. Aborting") $.assertConfig(.destination.Config.domain, "Gladly domain is not present. Aborting") $.assertConfig(.destination.Config.userName, "User Name is not present. Aborting") - - - name: queryParams - description: Build query params from payload to find user - template: | - const integrationsObj = $.getIntegrationsObj(.message, "gladly"); - $.getQueryParams(.message, integrationsObj) - - - name: findUser - description: Fetch user details if exists - condition: $.isDefinedAndNotNull($.outputs.queryParams) && $.isDefinedAndNotNull($.outputs.queryParams.key) && $.isDefinedAndNotNull($.outputs.queryParams.value) - template: | - const url = $.getEndpoint(.destination) + "?" + $.outputs.queryParams.key + "=" + $.outputs.queryParams.value - const requestOptions = { - headers: $.getHeaders(.destination) - } - const rawResponse = await $.handleHttpRequest("get", url, requestOptions) - const processedResponse = rawResponse.processedResponse - $.assertHttpResp(processedResponse, "Find User Lookup Failed due to " + JSON.stringify(processedResponse.response)) - processedResponse.response + $.assert(.message.userId, "userId is required") - name: preparePayload template: | @@ -60,34 +45,32 @@ steps: image: .message.context.traits.avatar || .message.traits.avatar, address: .message.context.traits.address || .message.traits.address } + $.context.payload.address ? $.context.payload.address = $.context.payload.address.toString() $.context.payload.emails = $.formatField(.message, "email") $.context.payload.phones = $.formatField(.message, "phone") $.context.payload.customAttributes = $.getCustomAttributes(.message) - $.context.payload.externalCustomerId = .message.context.externalId[0].id + $.context.payload.externalCustomerId = $.getDestinationExternalID(.message, "gladlyExternalCustomerId") $.context.payload = $.removeUndefinedAndNullValues($.context.payload) - name: createUser description: Prepare create user payload - condition: $.outputs.findUser.length === 0 || !$.isDefinedAndNotNull($.outputs.queryParams) - template: | - $.assert(.message.userId, "userId is required") - $.context.payload.id = .message.userId - $.context.method = "POST" - $.context.endpoint = $.getEndpoint(.destination) - - - name: updateUser - description: Prepare update user payload - condition: $.outputs.findUser.length > 0 template: | - $.context.method = "PATCH" - $.context.endpoint = $.getEndpoint(.destination) + "/" + .message.userId + const requestOptions = { + headers: $.getHeaders(.destination) + } + const requestPayload = {...$.context.payload, id: .message.userId} + const endpoint = $.getEndpoint(.destination) + const rawResponse = await $.httpPOST(endpoint, requestPayload, requestOptions) + const processedResponse = $.processAxiosResponse(rawResponse) + processedResponse - name: buildResponseForProcessTransformation - description: build response + description: build response for updateUser template: | const response = $.defaultRequestConfig() - response.body.JSON = $.context.payload - response.endpoint = $.context.endpoint - response.method = $.context.method + response.body.JSON = $.removeUndefinedAndNullValues($.context.payload) + response.endpoint = $.getEndpoint(.destination) + "/" + .message.userId + response.method = "PATCH" response.headers = $.getHeaders(.destination) - response; + response.status = $.outputs.createUser.status + response diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index 9de4640210..4561b1ebb2 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -139,8 +139,7 @@ const DestCanonicalNames = { 'twitter_ads', 'TWITTER_ADS', ], - BRAZE: ['BRAZE', 'Braze', 'braze'], - gladly: ['GLADLY', 'gladly', 'Gladly'] + BRAZE: ['BRAZE', 'Braze', 'braze'] }; module.exports = { DestHandlerMap, DestCanonicalNames }; From 0ea8b2bfe7f6d4a7ab7fbc89ddf3f76627c3fc7f Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 6 Nov 2023 18:45:04 +0530 Subject: [PATCH 04/15] chore: code review changes --- package-lock.json | 89 +++++++++++++------ package.json | 2 +- .../v2/destinations/gladly/procWorkflow.yaml | 5 +- .../v2/destinations/gladly/rtWorkflow.yaml | 6 +- src/cdk/v2/destinations/gladly/utils.js | 44 ++++----- src/cdk/v2/handler.ts | 7 +- src/services/destination/cdkV2Integration.ts | 7 +- 7 files changed, 95 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0822a9b42b..d988eccb57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/workflow-engine": "^0.5.7", + "@rudderstack/workflow-engine": "^0.6.8", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -6638,42 +6638,80 @@ } }, "node_modules/@rudderstack/json-template-engine": { - "version": "0.5.5", - "license": "MIT" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.1.tgz", + "integrity": "sha512-MR2ArfOXEDh9FEj/N3LVLjIxf134wq+YxUdZN4gTLEONIPdna97QeNk4hnhtlob0QQIrWr13mfPaU9FpvU2Q6Q==" }, "node_modules/@rudderstack/workflow-engine": { - "version": "0.5.7", - "license": "MIT", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.6.8.tgz", + "integrity": "sha512-fJRbFmr9sJCmRdANqpoaQJ9NnepZrKDzY7s8jhtezLOBI6jaRovFoBHx4djspqazGTlU33FkXHT96HxNeZ7zuA==", "dependencies": { - "@aws-crypto/sha256-js": "^4.0.0", - "@rudderstack/json-template-engine": "^0.5.5", - "js-yaml": "^4.1.0", + "@aws-crypto/sha256-js": "^5.0.0", + "@rudderstack/json-template-engine": "^0.8.1", "jsonata": "^2.0.3", "lodash": "^4.17.21", - "object-sizeof": "^2.6.3" + "object-sizeof": "^2.6.3", + "yaml": "^2.3.2" } }, "node_modules/@rudderstack/workflow-engine/node_modules/@aws-crypto/sha256-js": { - "version": "4.0.0", - "license": "Apache-2.0", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dependencies": { - "@aws-crypto/util": "^4.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/@rudderstack/workflow-engine/node_modules/@aws-crypto/util": { - "version": "4.0.0", - "license": "Apache-2.0", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dependencies": { "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@rudderstack/workflow-engine/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" + "node_modules/@rudderstack/workflow-engine/node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rudderstack/workflow-engine/node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rudderstack/workflow-engine/node_modules/@smithy/util-utf8": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.0.tgz", + "integrity": "sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/@sideway/address": { "version": "4.1.4", @@ -19352,8 +19390,9 @@ } }, "node_modules/tslib": { - "version": "2.5.3", - "license": "0BSD" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -19891,9 +19930,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.3.1", - "dev": true, - "license": "ISC", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "engines": { "node": ">= 14" } diff --git a/package.json b/package.json index 46f728664d..1503650123 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/workflow-engine": "^0.5.7", + "@rudderstack/workflow-engine": "^0.6.8", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index 5a4d3c337d..795aac9dac 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -3,8 +3,6 @@ bindings: path: ../../../../constants - path: ./utils exportAll: true - - name: isDefinedAndNotNull - path: ../../../../v0/util - name: defaultRequestConfig path: ../../../../v0/util - name: removeUndefinedAndNullValues @@ -45,7 +43,7 @@ steps: image: .message.context.traits.avatar || .message.traits.avatar, address: .message.context.traits.address || .message.traits.address } - $.context.payload.address ? $.context.payload.address = $.context.payload.address.toString() + $.context.payload.address && typeof $.context.payload.address === "object" ? $.context.payload.address = JSON.stringify($.context.payload.address) $.context.payload.emails = $.formatField(.message, "email") $.context.payload.phones = $.formatField(.message, "phone") $.context.payload.customAttributes = $.getCustomAttributes(.message) @@ -62,6 +60,7 @@ steps: const endpoint = $.getEndpoint(.destination) const rawResponse = await $.httpPOST(endpoint, requestPayload, requestOptions) const processedResponse = $.processAxiosResponse(rawResponse) + processedResponse.status == 400 ? $.assertHttpResp(processedResponse, "Unable to create or update user due to " + JSON.stringify(processedResponse.response)); processedResponse - name: buildResponseForProcessTransformation diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml index a907403ca4..bf08fd451f 100644 --- a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml @@ -1,6 +1,8 @@ bindings: - name: handleRtTfSingleEventError path: ../../../../v0/util/index + - path: ./utils + exportAll: true steps: - name: validateInput @@ -15,11 +17,11 @@ steps: - name: successfulEvents template: | $.outputs.transform#idx.output.({ - "batchedRequest": ., + "batchedRequest": $.getPayload(.), "batched": false, "destination": ^[idx].destination, "metadata": ^[idx].metadata[], - "statusCode": 200 + "statusCode": $.getStatusCode($.requestMetadata, $.outputs.transform#idx.output.status) })[] - name: failedEvents template: | diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index 17ee55bdc8..7aaf2ee359 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -1,6 +1,6 @@ -const { getFieldValueFromMessage, base64Convertor, getDestinationExternalID } = require('../../../../v0/util'); +const { getFieldValueFromMessage, base64Convertor, isNewStatusCodesAccepted } = require('../../../../v0/util'); +const { HTTP_STATUS_CODES } = require('../../../../v0/util/constant'); -const lookUpFields = ['email', 'phone', 'externalCustomerId']; const reservedCustomAttributes = [ 'email', 'phone', @@ -36,31 +36,6 @@ const formatField = (message, fieldName) => { return undefined; }; -const getQueryParams = (message, integrationsObj) => { - let queryParamKey; - let queryParamValue; - if (integrationsObj && integrationsObj.lookup){ - queryParamKey = integrationsObj.lookup; - } - if (!queryParamKey) { - queryParamKey = 'email'; - } - if (!lookUpFields.includes(queryParamKey)) { - queryParamKey = 'email'; - } - - if (queryParamKey === 'email' || queryParamKey === 'phone') { - queryParamValue = getFieldValueFromMessage(message,queryParamKey); - if (!queryParamValue) return undefined; - } else{ - queryParamValue = getDestinationExternalID(message, 'gladlyExternalCustomerId'); - if (!queryParamValue) return undefined; - } - - queryParamKey = queryParamKey === 'phone' ? 'phoneNumber' : queryParamKey; - return { key: queryParamKey, value: queryParamValue }; -}; - const getCustomAttributes = (message) => { const customAttributes = message.context.traits; reservedCustomAttributes.forEach((customAttribute) => { @@ -71,4 +46,17 @@ const getCustomAttributes = (message) => { return customAttributes; }; -module.exports = { formatField, getCustomAttributes, getEndpoint, getQueryParams, getHeaders }; +const getStatusCode = (requestMetadata, statusCode) => { + if(isNewStatusCodesAccepted(requestMetadata) && statusCode === 200){ + return HTTP_STATUS_CODES.SUPPRESS_EVENTS; + } + return statusCode; +} + +const getPayload = (eventPayload) => { + const payload = eventPayload; + delete payload.status; + return payload; +} + +module.exports = { formatField, getCustomAttributes, getEndpoint, getHeaders, getStatusCode, getPayload }; diff --git a/src/cdk/v2/handler.ts b/src/cdk/v2/handler.ts index 4b3868b85b..7dc0f5fd82 100644 --- a/src/cdk/v2/handler.ts +++ b/src/cdk/v2/handler.ts @@ -64,9 +64,9 @@ export function getCachedWorkflowEngine( return workflowEnginePromiseMap[destName][feature]; } -export async function executeWorkflow(workflowEngine: WorkflowEngine, parsedEvent: FixMe) { +export async function executeWorkflow(workflowEngine: WorkflowEngine, parsedEvent: FixMe, requestMetadata: NonNullable){ try { - const result = await workflowEngine.execute(parsedEvent); + const result = await workflowEngine.execute(parsedEvent, {requestMetadata}); // TODO: Handle remaining output scenarios return result.output; } catch (error) { @@ -78,11 +78,12 @@ export async function processCdkV2Workflow( destType: string, parsedEvent: FixMe, feature: string, + requestMetadata: NonNullable = {}, bindings: Record = {}, ) { try { const workflowEngine = await getCachedWorkflowEngine(destType, feature, bindings); - return await executeWorkflow(workflowEngine, parsedEvent); + return await executeWorkflow(workflowEngine, parsedEvent, requestMetadata); } catch (error) { throw getErrorInfo(error, isCdkV2Destination(parsedEvent), defTags); } diff --git a/src/services/destination/cdkV2Integration.ts b/src/services/destination/cdkV2Integration.ts index b4c0a15e87..672fe25245 100644 --- a/src/services/destination/cdkV2Integration.ts +++ b/src/services/destination/cdkV2Integration.ts @@ -52,7 +52,7 @@ export default class CDKV2DestinationService implements IntegrationDestinationSe events: ProcessorTransformationRequest[], destinationType: string, _version: string, - _requestMetadata: NonNullable, + requestMetadata: NonNullable, ): Promise { // TODO: Change the promise type const respList: ProcessorTransformationResponse[][] = await Promise.all( @@ -64,6 +64,7 @@ export default class CDKV2DestinationService implements IntegrationDestinationSe destinationType, event, tags.FEATURES.PROCESSOR, + requestMetadata ); stats.increment('event_transform_success', { @@ -108,7 +109,7 @@ export default class CDKV2DestinationService implements IntegrationDestinationSe events: RouterTransformationRequestData[], destinationType: string, _version: string, - _requestMetadata: NonNullable, + requestMetadata: NonNullable, ): Promise { const allDestEvents: object = groupBy( events, @@ -126,7 +127,7 @@ export default class CDKV2DestinationService implements IntegrationDestinationSe metaTo.metadata = destInputArray[0].metadata; try { const doRouterTransformationResponse: RouterTransformationResponse[] = - await processCdkV2Workflow(destinationType, destInputArray, tags.FEATURES.ROUTER); + await processCdkV2Workflow(destinationType, destInputArray, tags.FEATURES.ROUTER, requestMetadata); return DestinationPostTransformationService.handleRouterTransformSuccessEvents( doRouterTransformationResponse, undefined, From bdd8380b9dc24fd8fc3eacf56f1b54c8bbb5ee02 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 6 Nov 2023 19:07:27 +0530 Subject: [PATCH 05/15] chore: code review changes --- src/cdk/v2/destinations/gladly/rtWorkflow.yaml | 5 +++-- src/cdk/v2/destinations/gladly/utils.js | 8 +------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml index bf08fd451f..4230789632 100644 --- a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml @@ -16,12 +16,13 @@ steps: - name: successfulEvents template: | + const status = $.getStatusCode($.requestMetadata, $.outputs.transform#idx.output.status) $.outputs.transform#idx.output.({ - "batchedRequest": $.getPayload(.), + "batchedRequest": .{~["status"]}, "batched": false, "destination": ^[idx].destination, "metadata": ^[idx].metadata[], - "statusCode": $.getStatusCode($.requestMetadata, $.outputs.transform#idx.output.status) + "statusCode": status })[] - name: failedEvents template: | diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index 7aaf2ee359..18ee9c976e 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -53,10 +53,4 @@ const getStatusCode = (requestMetadata, statusCode) => { return statusCode; } -const getPayload = (eventPayload) => { - const payload = eventPayload; - delete payload.status; - return payload; -} - -module.exports = { formatField, getCustomAttributes, getEndpoint, getHeaders, getStatusCode, getPayload }; +module.exports = { formatField, getCustomAttributes, getEndpoint, getHeaders, getStatusCode }; From 264f326fe5e4c91162710ff99b1c153c1dbb40e4 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Fri, 10 Nov 2023 10:58:41 +0530 Subject: [PATCH 06/15] chore: code review changes --- src/cdk/v2/destinations/gladly/procWorkflow.yaml | 6 +++--- src/cdk/v2/destinations/gladly/utils.js | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index 795aac9dac..b11f75b06d 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -39,9 +39,9 @@ steps: - name: preparePayload template: | $.context.payload = { - name: .message.context.traits.name || .message.traits.name, - image: .message.context.traits.avatar || .message.traits.avatar, - address: .message.context.traits.address || .message.traits.address + name: .message.traits.name || .message.context.traits.name, + image: .message.traits.avatar || .message.context.traits.avatar, + address: .message.traits.address || .message.context.traits.address } $.context.payload.address && typeof $.context.payload.address === "object" ? $.context.payload.address = JSON.stringify($.context.payload.address) $.context.payload.emails = $.formatField(.message, "email") diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index 18ee9c976e..e9827b294f 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -37,6 +37,10 @@ const formatField = (message, fieldName) => { }; const getCustomAttributes = (message) => { + if (message.traits?.customAttributes && typeof message.traits?.customAttributes === 'object'){ + return message.traits?.customAttributes; + } + const customAttributes = message.context.traits; reservedCustomAttributes.forEach((customAttribute) => { if (customAttributes[customAttribute]) { From 87b8d9291af8b455a638be12162270b132cfeb88 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 13 Nov 2023 13:38:43 +0530 Subject: [PATCH 07/15] chore: code review changes --- .../v2/destinations/gladly/procWorkflow.yaml | 14 ++- src/cdk/v2/destinations/gladly/utils.js | 111 ++++++++++++++++-- 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index b11f75b06d..d51c59ec36 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -34,7 +34,6 @@ steps: $.assertConfig(.destination.Config.apiToken, "API Token is not present. Aborting") $.assertConfig(.destination.Config.domain, "Gladly domain is not present. Aborting") $.assertConfig(.destination.Config.userName, "User Name is not present. Aborting") - $.assert(.message.userId, "userId is required") - name: preparePayload template: | @@ -47,16 +46,21 @@ steps: $.context.payload.emails = $.formatField(.message, "email") $.context.payload.phones = $.formatField(.message, "phone") $.context.payload.customAttributes = $.getCustomAttributes(.message) - $.context.payload.externalCustomerId = $.getDestinationExternalID(.message, "gladlyExternalCustomerId") + $.context.payload.externalCustomerId = $.getExternalCustomerId(.message) + $.context.payload.id = $.getCustomerId(.message) $.context.payload = $.removeUndefinedAndNullValues($.context.payload) + - name: validatePayload + template: | + $.validatePayload($.context.payload) + - name: createUser description: Prepare create user payload template: | const requestOptions = { headers: $.getHeaders(.destination) } - const requestPayload = {...$.context.payload, id: .message.userId} + const requestPayload = $.context.payload const endpoint = $.getEndpoint(.destination) const rawResponse = await $.httpPOST(endpoint, requestPayload, requestOptions) const processedResponse = $.processAxiosResponse(rawResponse) @@ -67,8 +71,8 @@ steps: description: build response for updateUser template: | const response = $.defaultRequestConfig() - response.body.JSON = $.removeUndefinedAndNullValues($.context.payload) - response.endpoint = $.getEndpoint(.destination) + "/" + .message.userId + response.body.JSON = $.removeUndefinedAndNullValues($.context.payload.{~["id"]}) + response.endpoint = $.getEndpoint(.destination) + "/" + $.context.payload.id response.method = "PATCH" response.headers = $.getHeaders(.destination) response.status = $.outputs.createUser.status diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index e9827b294f..5eed2d9307 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -1,4 +1,11 @@ -const { getFieldValueFromMessage, base64Convertor, isNewStatusCodesAccepted } = require('../../../../v0/util'); +const get = require('get-value'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { + base64Convertor, + isNewStatusCodesAccepted, + getDestinationExternalID, +} = require('../../../../v0/util'); +const { MappedToDestinationKey } = require('../../../../constants'); const { HTTP_STATUS_CODES } = require('../../../../v0/util/constant'); const reservedCustomAttributes = [ @@ -12,6 +19,9 @@ const reservedCustomAttributes = [ 'userId', ]; +const externalIdKey = 'context.externalId.0.id'; +const identifierTypeKey = 'context.externalId.0.identifierType'; + const getHeaders = (destination) => { const { apiToken, userName } = destination.Config; return { @@ -25,8 +35,8 @@ const getEndpoint = (destination) => { return `https://${domain}/api/v1/customer-profiles`; }; -const formatField = (message, fieldName) => { - const field = getFieldValueFromMessage(message, fieldName); + +const getFieldValue = (field) => { if (field) { if (Array.isArray(field)) { return field.map((item) => ({ original: item })); @@ -34,14 +44,41 @@ const formatField = (message, fieldName) => { return [{ original: field }]; } return undefined; +} +const formatField = (message, fieldName) => { + let field; + const mappedToDestination = get(message, MappedToDestinationKey); + // for rETL + if (mappedToDestination) { + const identifierType = get(message, identifierTypeKey); + if (identifierType && identifierType === fieldName) { + field = get(message, externalIdKey); + if(field){ + return [{ original: field }]; + } + } + const key = fieldName === 'email' ? 'emails' : 'phones'; + field = get(message, `traits.${key}`); + return getFieldValue(field); + } + + // for event stream + field = get(message, `context.traits.${fieldName}`); + return getFieldValue(field); }; const getCustomAttributes = (message) => { - if (message.traits?.customAttributes && typeof message.traits?.customAttributes === 'object'){ - return message.traits?.customAttributes; + const mappedToDestination = get(message, MappedToDestinationKey); + // for rETL + if (mappedToDestination) { + if (message?.traits?.customAttributes && typeof message.traits.customAttributes === 'object') { + return message.traits.customAttributes; + } + return undefined; } - const customAttributes = message.context.traits; + // for event stream + const customAttributes = message.context.traits || {}; reservedCustomAttributes.forEach((customAttribute) => { if (customAttributes[customAttribute]) { delete customAttributes[customAttribute]; @@ -50,11 +87,67 @@ const getCustomAttributes = (message) => { return customAttributes; }; +const getExternalCustomerId = (message) => { + const mappedToDestination = get(message, MappedToDestinationKey); + // for rETL + if (mappedToDestination) { + const identifierType = get(message, identifierTypeKey); + if (identifierType === 'externalCustomerId') { + return get(message, externalIdKey); + } + + if (message?.traits?.externalCustomerId) { + return message.traits.externalCustomerId; + } + + return undefined; + } + + // for event stream + const externalCustomerId = getDestinationExternalID(message, 'GladlyExternalCustomerId'); + return externalCustomerId; +}; + +const getCustomerId = (message) => { + const mappedToDestination = get(message, MappedToDestinationKey); + // for rETL + if (mappedToDestination) { + const identifierType = get(message, identifierTypeKey); + if (identifierType === 'id') { + return get(message, externalIdKey); + } + + if (message?.traits?.id){ + return message.traits.id; + } + + return undefined; + } + + // for event stream + return message.userId; +}; + +const validatePayload = (payload) => { + if (!(payload.phones || payload.emails || payload.id || payload.externalCustomerId)) { + throw new InstrumentationError('One of phone, email, id or externalCustomerId is required for an identify call'); + } +}; + const getStatusCode = (requestMetadata, statusCode) => { - if(isNewStatusCodesAccepted(requestMetadata) && statusCode === 200){ + if (isNewStatusCodesAccepted(requestMetadata) && statusCode === 200) { return HTTP_STATUS_CODES.SUPPRESS_EVENTS; } return statusCode; -} +}; -module.exports = { formatField, getCustomAttributes, getEndpoint, getHeaders, getStatusCode }; +module.exports = { + getHeaders, + getEndpoint, + formatField, + getStatusCode, + getCustomerId, + validatePayload, + getCustomAttributes, + getExternalCustomerId +}; From 3460dc1e7a2126c191547bc56e0581ba80222b48 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 13 Nov 2023 15:54:18 +0530 Subject: [PATCH 08/15] chore: code review changes --- src/cdk/v2/destinations/gladly/utils.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index 5eed2d9307..a51fd7b194 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -45,6 +45,7 @@ const getFieldValue = (field) => { } return undefined; } + const formatField = (message, fieldName) => { let field; const mappedToDestination = get(message, MappedToDestinationKey); @@ -53,7 +54,7 @@ const formatField = (message, fieldName) => { const identifierType = get(message, identifierTypeKey); if (identifierType && identifierType === fieldName) { field = get(message, externalIdKey); - if(field){ + if (field) { return [{ original: field }]; } } @@ -104,8 +105,7 @@ const getExternalCustomerId = (message) => { } // for event stream - const externalCustomerId = getDestinationExternalID(message, 'GladlyExternalCustomerId'); - return externalCustomerId; + return message.userId; }; const getCustomerId = (message) => { @@ -117,7 +117,7 @@ const getCustomerId = (message) => { return get(message, externalIdKey); } - if (message?.traits?.id){ + if (message?.traits?.id) { return message.traits.id; } @@ -125,7 +125,12 @@ const getCustomerId = (message) => { } // for event stream - return message.userId; + const customerId = getDestinationExternalID(message, 'GladlyCustomerId'); + if (customerId) { + return customerId; + } + + return undefined; }; const validatePayload = (payload) => { @@ -138,6 +143,7 @@ const getStatusCode = (requestMetadata, statusCode) => { if (isNewStatusCodesAccepted(requestMetadata) && statusCode === 200) { return HTTP_STATUS_CODES.SUPPRESS_EVENTS; } + if(statusCode === 409)return 200; return statusCode; }; From af306a910d08eb0578ff12070156ba3ac9b36a80 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 13 Nov 2023 17:56:26 +0530 Subject: [PATCH 09/15] chore: code review changes --- .../v2/destinations/gladly/procWorkflow.yaml | 32 ++++++++++++------- .../v2/destinations/gladly/rtWorkflow.yaml | 5 ++- src/cdk/v2/destinations/gladly/utils.js | 24 ++++++++------ 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index d51c59ec36..5dd5fae8ff 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -9,7 +9,7 @@ bindings: path: ../../../../v0/util - name: getDestinationExternalID path: ../../../../v0/util - - name: httpPOST + - name: httpGET path: ../../../../adapters/network - name: processAxiosResponse path: ../../../../adapters/utils/networkUtils @@ -54,26 +54,36 @@ steps: template: | $.validatePayload($.context.payload) - - name: createUser - description: Prepare create user payload + - name: findCustomer + description: Find if customer is exist or not based on email, phone or externalCustomerId template: | const requestOptions = { headers: $.getHeaders(.destination) } - const requestPayload = $.context.payload - const endpoint = $.getEndpoint(.destination) - const rawResponse = await $.httpPOST(endpoint, requestPayload, requestOptions) + const endpoint = $.getEndpoint(.destination) + "?" + $.getQueryParams($.context.payload); + const rawResponse = await $.httpGET(endpoint,requestOptions) const processedResponse = $.processAxiosResponse(rawResponse) - processedResponse.status == 400 ? $.assertHttpResp(processedResponse, "Unable to create or update user due to " + JSON.stringify(processedResponse.response)); processedResponse - - name: buildResponseForProcessTransformation - description: build response for updateUser + - name: createCustomer + description: Build response for create customer + condition: $.outputs.findCustomer.status === 400 || ($.outputs.findCustomer.status === 200 && $.outputs.findCustomer.response.length === 0) + template: | + const response = $.defaultRequestConfig() + response.body.JSON = $.removeUndefinedAndNullValues($.context.payload) + response.endpoint = $.getEndpoint(.destination) + response.method = "POST" + response.headers = $.getHeaders(.destination) + console.log("res", $.outputs.findCustomer); + response + + - name: updateCustomer + description: Build response for update customer + condition: $.outputs.findCustomer.status === 200 && $.outputs.findCustomer.response.length > 0 template: | const response = $.defaultRequestConfig() response.body.JSON = $.removeUndefinedAndNullValues($.context.payload.{~["id"]}) - response.endpoint = $.getEndpoint(.destination) + "/" + $.context.payload.id + response.endpoint = $.getEndpoint(.destination) + "/" + $.outputs.findCustomer.response[0].id response.method = "PATCH" response.headers = $.getHeaders(.destination) - response.status = $.outputs.createUser.status response diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml index 4230789632..341e5552c8 100644 --- a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml @@ -16,13 +16,12 @@ steps: - name: successfulEvents template: | - const status = $.getStatusCode($.requestMetadata, $.outputs.transform#idx.output.status) $.outputs.transform#idx.output.({ - "batchedRequest": .{~["status"]}, + "batchedRequest": ., "batched": false, "destination": ^[idx].destination, "metadata": ^[idx].metadata[], - "statusCode": status + "statusCode": 200 })[] - name: failedEvents template: | diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index a51fd7b194..7e572a7188 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -2,11 +2,9 @@ const get = require('get-value'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { base64Convertor, - isNewStatusCodesAccepted, getDestinationExternalID, } = require('../../../../v0/util'); const { MappedToDestinationKey } = require('../../../../constants'); -const { HTTP_STATUS_CODES } = require('../../../../v0/util/constant'); const reservedCustomAttributes = [ 'email', @@ -139,20 +137,28 @@ const validatePayload = (payload) => { } }; -const getStatusCode = (requestMetadata, statusCode) => { - if (isNewStatusCodesAccepted(requestMetadata) && statusCode === 200) { - return HTTP_STATUS_CODES.SUPPRESS_EVENTS; +const getQueryParams = (payload) => { + if (payload.emails && payload.emails.length > 0) { + return `email=${encodeURIComponent(payload.emails[0].original)}` } - if(statusCode === 409)return 200; - return statusCode; -}; + + if (payload.phones && payload.phones.length > 0) { + return `phoneNumber=${encodeURIComponent(payload.phones[0].original)}` + } + + if (payload.externalCustomerId) { + return `externalCustomerId=${encodeURIComponent(payload.externalCustomerId)}` + } + + return undefined; +} module.exports = { getHeaders, getEndpoint, formatField, - getStatusCode, getCustomerId, + getQueryParams, validatePayload, getCustomAttributes, getExternalCustomerId From b299783105423c29d4b369f0b718055a43312c30 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Tue, 14 Nov 2023 09:45:34 +0530 Subject: [PATCH 10/15] fix: package.json changes --- package-lock.json | 68 ++++++++++++++++++++++++++++++++++++++++------- package.json | 2 +- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index a149b6685d..358207cf1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,8 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/workflow-engine": "^0.6.8", "@rudderstack/integrations-lib": "^0.1.8", + "@rudderstack/workflow-engine": "^0.6.9", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -6657,15 +6657,63 @@ "tslib": "^2.4.0" } }, + "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-4.0.0.tgz", + "integrity": "sha512-MHGJyjE7TX9aaqXj7zk2ppnFUOhaDs5sP+HtNS0evOxn72c+5njUmyJmpGd7TfyoDznZlHMmdo/xGUdu2NIjNQ==", + "dependencies": { + "@aws-crypto/util": "^4.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-4.0.0.tgz", + "integrity": "sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/json-template-engine": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.5.5.tgz", + "integrity": "sha512-p3HdTqgZiJjjZmjaHN2paa1e87ifGE5UjkA4zdvge4bBzJbKKMQNWqRg6I96SwoA+hsxNkW/f9R83SPLU9t7LA==" + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/workflow-engine": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.5.8.tgz", + "integrity": "sha512-H1aCowYqTnOoqJtL9cGDhdhoGNl+KzqmVbSjFmE7n75onZaIMs87+HQyW08jYxS9l1Uo4TL8SAvzFICqFqkBbw==", + "dependencies": { + "@aws-crypto/sha256-js": "^4.0.0", + "@rudderstack/json-template-engine": "^0.5.5", + "js-yaml": "^4.1.0", + "jsonata": "^2.0.3", + "lodash": "^4.17.21", + "object-sizeof": "^2.6.3" + } + }, "node_modules/@rudderstack/json-template-engine": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.1.tgz", - "integrity": "sha512-MR2ArfOXEDh9FEj/N3LVLjIxf134wq+YxUdZN4gTLEONIPdna97QeNk4hnhtlob0QQIrWr13mfPaU9FpvU2Q6Q==" + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.2.tgz", + "integrity": "sha512-9oMBnqgNuwiXd7MUlNOAchCnJXQAy6w6XGmDqDM6iXdYDkvqYFiq7sbg5j4SdtpTTST293hahREr5PXfFVzVKg==" }, "node_modules/@rudderstack/workflow-engine": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.6.8.tgz", - "integrity": "sha512-fJRbFmr9sJCmRdANqpoaQJ9NnepZrKDzY7s8jhtezLOBI6jaRovFoBHx4djspqazGTlU33FkXHT96HxNeZ7zuA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.6.9.tgz", + "integrity": "sha512-b0ZHURJfCj2REIL/w7AJgJ+K5BGwIVX3sRDZQqN3F4YWcZX3ZYUXo7gtUeb99FLnZzm7KuThIWR02Fxwos+L4Q==", "dependencies": { "@aws-crypto/sha256-js": "^5.0.0", "@rudderstack/json-template-engine": "^0.8.1", @@ -6722,9 +6770,9 @@ } }, "node_modules/@rudderstack/workflow-engine/node_modules/@smithy/util-utf8": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.0.tgz", - "integrity": "sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", + "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", "dependencies": { "@smithy/util-buffer-from": "^2.0.0", "tslib": "^2.5.0" diff --git a/package.json b/package.json index 3735b37916..a478f04372 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,8 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/workflow-engine": "^0.6.8", "@rudderstack/integrations-lib": "^0.1.8", + "@rudderstack/workflow-engine": "^0.6.9", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", From f8ba7097af2db6d35d84176bed033fa8d7e768de Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Tue, 14 Nov 2023 11:39:26 +0530 Subject: [PATCH 11/15] chore: added utility tests --- .../v2/destinations/gladly/procWorkflow.yaml | 4 +- src/cdk/v2/destinations/gladly/utils.js | 50 +- src/cdk/v2/destinations/gladly/utils.test.js | 503 ++++++++++++++++++ src/cdk/v2/handler.ts | 2 +- 4 files changed, 535 insertions(+), 24 deletions(-) create mode 100644 src/cdk/v2/destinations/gladly/utils.test.js diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index 5dd5fae8ff..aa4cc3ffb5 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -56,6 +56,7 @@ steps: - name: findCustomer description: Find if customer is exist or not based on email, phone or externalCustomerId + condition: $.getQueryParams($.context.payload) !== undefined template: | const requestOptions = { headers: $.getHeaders(.destination) @@ -67,14 +68,13 @@ steps: - name: createCustomer description: Build response for create customer - condition: $.outputs.findCustomer.status === 400 || ($.outputs.findCustomer.status === 200 && $.outputs.findCustomer.response.length === 0) + condition: $.outputs.findCustomer.status === 400 || ($.outputs.findCustomer.status === 200 && $.outputs.findCustomer.response.length === 0) || $.getQueryParams($.context.payload) === undefined template: | const response = $.defaultRequestConfig() response.body.JSON = $.removeUndefinedAndNullValues($.context.payload) response.endpoint = $.getEndpoint(.destination) response.method = "POST" response.headers = $.getHeaders(.destination) - console.log("res", $.outputs.findCustomer); response - name: updateCustomer diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index 7e572a7188..d01b48b540 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -44,26 +44,31 @@ const getFieldValue = (field) => { return undefined; } +const formatFieldForRETl = (message, fieldName) => { + const identifierType = get(message, identifierTypeKey); + if (identifierType && identifierType === fieldName) { + const field = get(message, externalIdKey); + if (field) { + return [{ original: field }]; + } + } + const key = fieldName === 'email' ? 'emails' : 'phones'; + const field = get(message, `traits.${key}`); + return getFieldValue(field); +}; + +const formatFieldForEventStream = (message, fieldName) => { + const field = get(message, `context.traits.${fieldName}`); + return getFieldValue(field); +}; + const formatField = (message, fieldName) => { - let field; const mappedToDestination = get(message, MappedToDestinationKey); - // for rETL if (mappedToDestination) { - const identifierType = get(message, identifierTypeKey); - if (identifierType && identifierType === fieldName) { - field = get(message, externalIdKey); - if (field) { - return [{ original: field }]; - } - } - const key = fieldName === 'email' ? 'emails' : 'phones'; - field = get(message, `traits.${key}`); - return getFieldValue(field); + return formatFieldForRETl(message, fieldName); } + return formatFieldForEventStream(message, fieldName); - // for event stream - field = get(message, `context.traits.${fieldName}`); - return getFieldValue(field); }; const getCustomAttributes = (message) => { @@ -71,19 +76,19 @@ const getCustomAttributes = (message) => { // for rETL if (mappedToDestination) { if (message?.traits?.customAttributes && typeof message.traits.customAttributes === 'object') { - return message.traits.customAttributes; + return Object.keys(message.traits.customAttributes).length > 0 ? message.traits.customAttributes : undefined; } return undefined; } // for event stream - const customAttributes = message.context.traits || {}; + const customAttributes = message.context?.traits || {}; reservedCustomAttributes.forEach((customAttribute) => { if (customAttributes[customAttribute]) { delete customAttributes[customAttribute]; } }); - return customAttributes; + return Object.keys(customAttributes).length > 0 ? customAttributes : undefined; }; const getExternalCustomerId = (message) => { @@ -132,8 +137,8 @@ const getCustomerId = (message) => { }; const validatePayload = (payload) => { - if (!(payload.phones || payload.emails || payload.id || payload.externalCustomerId)) { - throw new InstrumentationError('One of phone, email, id or externalCustomerId is required for an identify call'); + if (!(payload?.phones || payload?.emails || payload?.id || payload?.externalCustomerId)) { + throw new InstrumentationError('One of phone, email, userId or GladlyCustomerId is required for an identify call'); } }; @@ -157,9 +162,12 @@ module.exports = { getHeaders, getEndpoint, formatField, + getFieldValue, getCustomerId, getQueryParams, validatePayload, + formatFieldForRETl, getCustomAttributes, - getExternalCustomerId + getExternalCustomerId, + formatFieldForEventStream }; diff --git a/src/cdk/v2/destinations/gladly/utils.test.js b/src/cdk/v2/destinations/gladly/utils.test.js new file mode 100644 index 0000000000..116f150448 --- /dev/null +++ b/src/cdk/v2/destinations/gladly/utils.test.js @@ -0,0 +1,503 @@ +const { + getHeaders, + getEndpoint, + formatField, + getCustomerId, + getFieldValue, + getQueryParams, + validatePayload, + formatFieldForRETl, + getCustomAttributes, + getExternalCustomerId, + formatFieldForEventStream, +} = require('./utils'); +const { base64Convertor } = require('../../../../v0/util'); + +describe('Unit test cases for getHeaders function', () => { + it('Should return headers', () => { + const destination = { + Config: { + apiToken: 'token', + userName: 'user', + }, + }; + const expectedHeaders = { + 'Content-Type': 'application/json', + Authorization: `Basic ${base64Convertor('user:token')}`, + }; + + const result = getHeaders(destination); + + expect(result).toEqual(expectedHeaders); + }); +}); + +describe('Unit test cases for getEndpoint function', () => { + it('Should return destination endpoint', () => { + const destination = { + Config: { + domain: 'rudderstack.us-uat.gladly.qa', + }, + }; + const expected = 'https://rudderstack.us-uat.gladly.qa/api/v1/customer-profiles'; + const result = getEndpoint(destination); + expect(result).toBe(expected); + }); +}); + +describe('Unit test cases for getFieldValue function', () => { + it('Should return an array with a single object containing the original value when the input field is a string', () => { + const field = 'rudderlabs1@gmail.com'; + const result = getFieldValue(field); + expect(result).toEqual([{ original: field }]); + }); + + it('should return an array with each element containing the original value when the input field is an array', () => { + const field = ['rudderlabs1@gmail.com', 'rudderlabs2@gmail.com', 'rudderlabs3@gmail.com']; + const result = getFieldValue(field); + expect(result).toEqual([ + { + original: 'rudderlabs1@gmail.com', + }, + { + original: 'rudderlabs2@gmail.com', + }, + { + original: 'rudderlabs3@gmail.com', + }, + ]); + }); + + it('Should return undefined when the input field is null', () => { + const field = null; + const result = getFieldValue(field); + expect(result).toBeUndefined(); + }); + + it('Should return undefined when the input field is undefined', () => { + const field = undefined; + const result = getFieldValue(field); + expect(result).toBeUndefined(); + }); +}); + +describe('Unit test cases for formatFieldForRETl function', () => { + it('should return the object containing the original value when identifierType matches fieldName', () => { + const message = { + context: { + externalId: [ + { + id: 'test@rudderlabs.com', + identifierType: 'email', + }, + ], + }, + traits: { + emails: ['test@rudderlabs.com', 'test@rudderlabshome.com'], + }, + }; + const fieldName = 'email'; + const expected = [{ original: 'test@rudderlabs.com' }]; + + const result = formatFieldForRETl(message, fieldName); + + expect(result).toEqual(expected); + }); + + it('Should retrieve the email value from traits when fieldName does not match with identifierType', () => { + const message = { + context: { + externalId: [ + { + id: '+91 9999999999', + identifierType: 'phone', + }, + ], + }, + traits: { + emails: ['test@rudderlabs.com', 'test@rudderlabshome.com'], + }, + }; + const fieldName = 'email'; + const expected = [{ original: 'test@rudderlabs.com' }, { original: 'test@rudderlabshome.com' }]; + + const result = formatFieldForRETl(message, fieldName); + + expect(result).toEqual(expected); + }); +}); + +describe('Unit test cases for formatFieldForEventStream function', () => { + it('Should return field value when fieldName exist in payload', () => { + const message = { + context: { + traits: { + phone: '+91 9999999999', + }, + }, + }; + const fieldName = 'phone'; + const expected = [{ original: '+91 9999999999' }]; + + const result = formatFieldForEventStream(message, fieldName); + expect(result).toEqual(expected); + }); + + it('Should return undefined when fieldName does not exist in payload', () => { + const message = { + context: { + traits: { + phone: '+91 9999999999', + }, + }, + }; + const fieldName = 'email'; + const result = formatFieldForEventStream(message, fieldName); + expect(result).toBeUndefined(); + }); +}); + +describe('Unit test cases for formatField function', () => { + describe('rETL tests', () => { + it('Should return field value from externalId when identifier type matches with fieldName', () => { + const message = { + context: { + externalId: [ + { + id: '+91 9999999999', + identifierType: 'phone', + }, + ], + mappedToDestination: true, + }, + traits: { + emails: ['test@rudderlabs.com', 'test@rudderlabshome.com'], + }, + }; + const result = formatField(message, 'phone'); + expect(result).toEqual([{ original: '+91 9999999999' }]); + }); + + it('Should return field value from traits when identifier type does not match with fieldName', () => { + const message = { + context: { + externalId: [ + { + id: 'user@1', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true, + }, + traits: { + phones: ['+91 9999999999'], + }, + }; + const result = formatField(message, 'phone'); + expect(result).toEqual([{ original: '+91 9999999999' }]); + }); + }); + + describe('Event stream tests', () => { + it('Should return field value from payload', () => { + const message = { + context: { + traits: { + phone: ['+91 9999999999'], + }, + }, + }; + const result = formatField(message, 'phone'); + expect(result).toEqual([{ original: '+91 9999999999' }]); + }); + }); +}); + +describe('Unit test cases for getCustomAttributes function', () => { + describe('rETL tests', () => { + it('Should return custom attributes from payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + traits: { + customAttributes: { + attribute1: 'value1', + attribute2: 'value2', + }, + }, + }; + const result = getCustomAttributes(message); + expect(result).toEqual({ + attribute1: 'value1', + attribute2: 'value2', + }); + }); + + it('Should return undefined when empty custom attributes object is present in payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + traits: { + customAttributes: {}, + }, + }; + const result = getCustomAttributes(message); + expect(result).toBeUndefined(); + }); + + it('Should return undefined when no custom attributes are present in payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + traits: {}, + }; + const result = getCustomAttributes(message); + expect(result).toBeUndefined(); + }); + }); + + describe('Event stream tests', () => { + it('Should filter traits and return remaining custom attributes from payload', () => { + const message = { + context: { + traits: { + name: 'John Doe', + email: 'john@gmail.com', + age: 65, + source: 'rudderstack', + }, + }, + }; + const result = getCustomAttributes(message); + expect(result).toEqual({ + age: 65, + source: 'rudderstack', + }); + }); + + it('Should return undefined when empty traits object is present in payload', () => { + const message = { + context: { + traits: {}, + }, + }; + const result = getCustomAttributes(message); + expect(result).toBeUndefined(); + }); + + it('Should return undefined when no traits object is present in payload', () => { + const message = { + context: {}, + }; + const result = getCustomAttributes(message); + expect(result).toBeUndefined(); + }); + }); +}); + +describe('Unit test cases for getExternalCustomerId function', () => { + describe('rETL tests', () => { + it('Should return the external ID when the identifier type is "externalCustomerId"', () => { + const message = { + context: { + externalId: [ + { + id: 'externalCustomer@1', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true, + }, + }; + + const result = getExternalCustomerId(message); + expect(result).toBe('externalCustomer@1'); + }); + + it('Should return the external ID from traits when identifier type is not "externalCustomerId"', () => { + const message = { + context: { + externalId: [ + { + id: 'test@rudderlabs.com', + identifierType: 'email', + }, + ], + mappedToDestination: true, + }, + traits: { + externalCustomerId: 'externalCustomer@1', + }, + }; + const result = getExternalCustomerId(message); + expect(result).toBe('externalCustomer@1'); + }); + + it('Should return undefined when external customer id is not present in payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + }; + + const result = getExternalCustomerId(message); + expect(result).toBeUndefined(); + }); + }); + + describe('Event stream tests', () => { + it('Should return the external ID as userId is present in payload', () => { + const message = { + userId: 'externalCustomer@1', + context: {}, + }; + + const result = getExternalCustomerId(message); + expect(result).toBe('externalCustomer@1'); + }); + + it('Should return undefined when userId is not present in payload', () => { + const message = { + context: {}, + }; + + const result = getExternalCustomerId(message); + expect(result).toBeUndefined(); + }); + }); +}); + +describe('Unit test cases for getCustomerId function', () => { + describe('rETL tests', () => { + it('Should return the customerId when the identifier type is "id"', () => { + const message = { + context: { + externalId: [ + { + id: 'user@1', + identifierType: 'id', + }, + ], + mappedToDestination: true, + }, + }; + + const result = getCustomerId(message); + expect(result).toBe('user@1'); + }); + + it('Should return the customerId from traits when identifier type is not "id"', () => { + const message = { + context: { + externalId: [ + { + id: 'test@rudderlabs.com', + identifierType: 'email', + }, + ], + mappedToDestination: true, + }, + traits: { + id: 'user@1', + }, + }; + const result = getCustomerId(message); + expect(result).toBe('user@1'); + }); + + it('Should return undefined when customerId is not present in payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + }; + + const result = getCustomerId(message); + expect(result).toBeUndefined(); + }); + }); + + describe('Event stream tests', () => { + it('Should return the customerId as GladlyCustomerId is present in payload', () => { + const message = { + context: { + externalId: [ + { + id: 'user@1', + type: 'GladlyCustomerId', + }, + ], + }, + }; + const result = getCustomerId(message); + expect(result).toBe('user@1'); + }); + + it('Should return undefined when GladlyCustomerId is not present in payload', () => { + const message = { + context: {}, + }; + const result = getCustomerId(message); + expect(result).toBeUndefined(); + }); + }); +}); + +describe('Unit test cases for validatePayload function', () => { + it('Should throw an error when payload does not have all required fields', () => { + const payload = {}; + try { + validatePayload(payload); + } catch (err) { + expect(err.message).toEqual( + 'One of phone, email, userId or GladlyCustomerId is required for an identify call', + ); + } + }); + + it('Should throw an error when payload is undefined', () => { + const payload = undefined; + try { + validatePayload(payload); + } catch (err) { + expect(err.message).toEqual( + 'One of phone, email, userId or GladlyCustomerId is required for an identify call', + ); + } + }); +}); + +describe('Unit test cases for getQueryParams function', () => { + it('Should return email as query parameter if email is present in payload', () => { + const payload = { + emails: [{ original: 'test@example.com' }], + }; + const result = getQueryParams(payload); + expect(result).toBe('email=test%40example.com'); + }); + + it('Should return phone as query parameter if phone is present in payload', () => { + const payload = { + phones: [{ original: '+91 9999999999' }], + }; + const result = getQueryParams(payload); + expect(result).toBe('phoneNumber=%2B91%209999999999'); + }); + + it('Should return externalCustomerId as query parameter if externalCustomerId is present in payload', () => { + const payload = { + externalCustomerId: 'externalCustomer@1', + }; + const result = getQueryParams(payload); + expect(result).toBe('externalCustomerId=externalCustomer%401'); + }); + + it('should return undefined when no supported query params are present in payload', () => { + const payload = {}; + const result = getQueryParams(payload); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/cdk/v2/handler.ts b/src/cdk/v2/handler.ts index 9e50a05997..47d6d10179 100644 --- a/src/cdk/v2/handler.ts +++ b/src/cdk/v2/handler.ts @@ -67,7 +67,7 @@ export function getCachedWorkflowEngine( export async function executeWorkflow( workflowEngine: WorkflowEngine, parsedEvent: FixMe, - requestMetadata: NonNullable, + requestMetadata: NonNullable = {}, ) { const result = await workflowEngine.execute(parsedEvent, { requestMetadata }); // TODO: Handle remaining output scenarios From 7186398caefb8177c3cb22e5a6e24d8c2f848d62 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Tue, 14 Nov 2023 14:30:06 +0530 Subject: [PATCH 12/15] chore: added gladly router and processor tests --- .../v2/destinations/gladly/procWorkflow.yaml | 2 +- src/cdk/v2/destinations/gladly/utils.js | 2 +- .../destinations/gladly/network.ts | 120 +++ .../destinations/gladly/processor/data.ts | 809 ++++++++++++++++++ .../destinations/gladly/router/data.ts | 304 +++++++ 5 files changed, 1235 insertions(+), 2 deletions(-) create mode 100644 test/integrations/destinations/gladly/network.ts create mode 100644 test/integrations/destinations/gladly/processor/data.ts create mode 100644 test/integrations/destinations/gladly/router/data.ts diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index aa4cc3ffb5..6a0272c585 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -29,7 +29,7 @@ steps: - name: validateInput template: | let messageType = $.outputs.messageType - $.assert(messageType, "message Type is not present. Aborting.") + $.assert(messageType, "message Type is not present. Aborting") $.assert(messageType in {{$.EventType.([.IDENTIFY])}}, "message type " + messageType + " is not supported") $.assertConfig(.destination.Config.apiToken, "API Token is not present. Aborting") $.assertConfig(.destination.Config.domain, "Gladly domain is not present. Aborting") diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index d01b48b540..2b9459fb58 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -13,7 +13,7 @@ const reservedCustomAttributes = [ 'name', 'avatar', 'firstName', - 'lstName', + 'lastName', 'userId', ]; diff --git a/test/integrations/destinations/gladly/network.ts b/test/integrations/destinations/gladly/network.ts new file mode 100644 index 0000000000..8c1c228738 --- /dev/null +++ b/test/integrations/destinations/gladly/network.ts @@ -0,0 +1,120 @@ +const deleteNwData = [ + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?email=test%40rudderlabs.com', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: [], + status: 200, + }, + }, + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?email=test%2B2%40rudderlabs.com', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: [ + { + emails: [ + { + normalized: 'test+2@rudderstack.com', + original: 'test+2@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@2', + name: 'Test Rudderstack', + phones: [], + id: 'user@2', + }, + ], + status: 200, + }, + }, + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?phoneNumber=%2B91%209999999988', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: [ + { + emails: [ + { + normalized: 'test+3@rudderstack.com', + original: 'test+3@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@3', + name: 'Test Rudderstack', + phones: [], + id: 'user@3', + }, + { + emails: [ + { + normalized: 'test+4@rudderstack.com', + original: 'test+4@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@4', + name: 'Test Rudderstack', + phones: [], + id: 'user@4', + }, + ], + status: 200, + }, + }, + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?email=test6%40rudderlabs.com', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: [], + status: 200, + }, + }, + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?email=abc', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: { + errors: [ + { + attr: 'email', + code: 'invalid', + detail: 'invalid email address', + }, + ], + }, + status: 400, + }, + }, +]; + +export const networkCallsData = [...deleteNwData]; diff --git a/test/integrations/destinations/gladly/processor/data.ts b/test/integrations/destinations/gladly/processor/data.ts new file mode 100644 index 0000000000..211fa78134 --- /dev/null +++ b/test/integrations/destinations/gladly/processor/data.ts @@ -0,0 +1,809 @@ +export const data = [ + { + name: 'gladly', + description: 'No message type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstName: 'Test', + }, + }, + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 1, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 1, + }, + statusCode: 400, + error: + 'message Type is not present. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message Type is not present. Aborting', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'GLADLY', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Unsupported message type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstName: 'Test', + }, + }, + event: 'Product Viewed', + type: 'track', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 2, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 2, + }, + statusCode: 400, + error: + 'message type track is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type track is not supported', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'GLADLY', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Missing config', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstName: 'Test', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 3, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 3, + }, + statusCode: 400, + error: + 'User Name is not present. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: User Name is not present. Aborting', + statTags: { + errorCategory: 'dataValidation', + errorType: 'configuration', + destType: 'GLADLY', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Create customer with email as lookup field', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'externalCustomer@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test@rudderlabs.com', + phone: '+91 9999999999', + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'california usa', + }, + externalId: [ + { + id: 'user@1', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 4, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'california usa', + customAttributes: { age: 23 }, + emails: [{ original: 'test@rudderlabs.com' }], + externalCustomerId: 'externalCustomer@1', + id: 'user@1', + phones: [{ original: '+91 9999999999' }], + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 4 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Update customer with email as lookup field', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'externalCustomer@2', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test+2@rudderlabs.com', + phone: '+91 9999999998', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + externalId: [ + { + id: 'user@2', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 5, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'New York, USA', + customAttributes: { age: 23 }, + emails: [{ original: 'test+2@rudderlabs.com' }], + externalCustomerId: 'externalCustomer@2', + phones: [{ original: '+91 9999999998' }], + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@2', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'PATCH', + files: {}, + params: {}, + }, + metadata: { jobId: 5 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Update customer with phone as lookup field', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'externalCustomer@3', + channel: 'web', + context: { + traits: { + phone: '+91 9999999988', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + externalId: [ + { + id: 'user@3', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 6, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'New York, USA', + externalCustomerId: 'externalCustomer@3', + phones: [{ original: '+91 9999999988' }], + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@3', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'PATCH', + files: {}, + params: {}, + }, + metadata: { jobId: 6 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Required values are not present in payload to create or update customer', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: { + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + }, + type: 'identify', + anonymousId: '78c53c15-32a1-4b65-adac-bec2d7bb8fab', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 7, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 7, + }, + statusCode: 400, + error: + 'One of phone, email, userId or GladlyCustomerId is required for an identify call: Workflow: procWorkflow, Step: validatePayload, ChildStep: undefined, OriginalError: One of phone, email, userId or GladlyCustomerId is required for an identify call', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'GLADLY', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Multiple emails and phones are present in payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'externalCustomer@6', + channel: 'web', + context: { + traits: { + age: 23, + email: [ + 'test6@rudderlabs.com', + 'test6home@rudderlabs.com', + 'test6office@rudderlabs.com', + ], + phone: ['+91 8888888888', '+91 8888888889'], + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'Germany', + }, + externalId: [ + { + id: 'user@6', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 8, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'Germany', + customAttributes: { age: 23 }, + emails: [ + { original: 'test6@rudderlabs.com' }, + { original: 'test6home@rudderlabs.com' }, + { original: 'test6office@rudderlabs.com' }, + ], + externalCustomerId: 'externalCustomer@6', + id: 'user@6', + phones: [{ original: '+91 8888888888' }, { original: '+91 8888888889' }], + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 8 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Create customer with only GladlyCustomerId', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: { + firstName: 'Test', + lastName: 'Undefined', + address: 'India', + isProUser: true, + }, + externalId: [ + { + id: 'user@9', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 9, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'India', + customAttributes: { isProUser: true }, + id: 'user@9', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 9 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Create customer with invalid lookup field value', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: { + firstName: 'Test', + lastName: 'Undefined', + address: 'Pakistan', + email: 'abc', + }, + externalId: [ + { + id: 'user@10', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 10, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'Pakistan', + emails: [{original: 'abc'}], + id: 'user@10', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 10 }, + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/gladly/router/data.ts b/test/integrations/destinations/gladly/router/data.ts new file mode 100644 index 0000000000..967b512c52 --- /dev/null +++ b/test/integrations/destinations/gladly/router/data.ts @@ -0,0 +1,304 @@ +export const data = [ + { + name: 'gladly', + description: 'Gladly router tests', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + userId: 'externalCustomer@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test@rudderlabs.com', + phone: '+91 9999999999', + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'california usa', + }, + externalId: [ + { + id: 'user@1', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 1, + }, + }, + { + message: { + userId: 'externalCustomer@2', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test+2@rudderlabs.com', + phone: '+91 9999999998', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + externalId: [ + { + id: 'user@2', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 2, + }, + }, + { + message: { + userId: 'externalCustomer@3', + channel: 'web', + context: { + traits: { + phone: '+91 9999999988', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + externalId: [ + { + id: 'user@3', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 3, + }, + }, + ], + destType: 'gladly', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'california usa', + customAttributes: { + age: 23, + }, + emails: [ + { + original: 'test@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@1', + id: 'user@1', + phones: [ + { + original: '+91 9999999999', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 1, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'New York, USA', + customAttributes: { + age: 23, + }, + emails: [ + { + original: 'test+2@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@2', + phones: [ + { + original: '+91 9999999998', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@2', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 2, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'New York, USA', + externalCustomerId: 'externalCustomer@3', + phones: [ + { + original: '+91 9999999988', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@3', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 3, + }, + ], + statusCode: 200, + }, + ], + }, + }, + }, + }, +]; From 0f0b4198bebe4c8097b8231d41e938a9617216a5 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Tue, 14 Nov 2023 14:41:56 +0530 Subject: [PATCH 13/15] chore: added gladly rETL tests --- .../destinations/gladly/router/data.ts | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) diff --git a/test/integrations/destinations/gladly/router/data.ts b/test/integrations/destinations/gladly/router/data.ts index 967b512c52..d3339d8108 100644 --- a/test/integrations/destinations/gladly/router/data.ts +++ b/test/integrations/destinations/gladly/router/data.ts @@ -301,4 +301,304 @@ export const data = [ }, }, }, + { + name: 'gladly', + description: 'Gladly rETL tests', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + userId: 'externalCustomer@1', + channel: 'web', + context: { + externalId: [ + { + id: 'externalCustomer@1', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true + }, + traits: { + id: 'user@1', + emails: ['test@rudderlabs.com'], + phones: ['+91 9999999999'], + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'california usa', + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 1, + }, + }, + { + message: { + userId: 'externalCustomer@2', + channel: 'web', + context: { + externalId: [ + { + id: 'externalCustomer@2', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true, + }, + traits: { + id: 'user@2', + emails: 'test+2@rudderlabs.com', + phones: '+91 9999999998', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 2, + }, + }, + { + message: { + userId: 'externalCustomer@3', + channel: 'web', + context: { + externalId: [ + { + id: 'externalCustomer@3', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true, + }, + traits: { + id: 'user@3', + phones: '+91 9999999988', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 3, + }, + }, + ], + destType: 'gladly', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'california usa', + emails: [ + { + original: 'test@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@1', + id: 'user@1', + phones: [ + { + original: '+91 9999999999', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 1, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'New York, USA', + emails: [ + { + original: 'test+2@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@2', + phones: [ + { + original: '+91 9999999998', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@2', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 2, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'New York, USA', + externalCustomerId: 'externalCustomer@3', + phones: [ + { + original: '+91 9999999988', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@3', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 3, + }, + ], + statusCode: 200, + }, + ], + }, + }, + }, + }, ]; From 725939f448e7104791a5f4b4d36922931133ee6d Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Tue, 14 Nov 2023 14:52:19 +0530 Subject: [PATCH 14/15] fix: sonar code smell --- src/cdk/v2/destinations/gladly/utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js index 2b9459fb58..5abc9b6dd0 100644 --- a/src/cdk/v2/destinations/gladly/utils.js +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -22,9 +22,11 @@ const identifierTypeKey = 'context.externalId.0.identifierType'; const getHeaders = (destination) => { const { apiToken, userName } = destination.Config; + const credentials = `${userName}:${apiToken}`; + const base64Credentials = base64Convertor(credentials); return { 'Content-Type': 'application/json', - Authorization: `Basic ${base64Convertor(`${userName}:${apiToken}`)}`, + Authorization: `Basic ${base64Credentials}`, }; }; From 6c117235d89827830aeca86292e7cfc6d4c5b8d7 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Tue, 14 Nov 2023 16:57:05 +0530 Subject: [PATCH 15/15] chore: code review changes --- .../v2/destinations/gladly/procWorkflow.yaml | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index 6a0272c585..fe8697bc31 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -76,14 +76,13 @@ steps: response.method = "POST" response.headers = $.getHeaders(.destination) response - - - name: updateCustomer - description: Build response for update customer - condition: $.outputs.findCustomer.status === 200 && $.outputs.findCustomer.response.length > 0 - template: | - const response = $.defaultRequestConfig() - response.body.JSON = $.removeUndefinedAndNullValues($.context.payload.{~["id"]}) - response.endpoint = $.getEndpoint(.destination) + "/" + $.outputs.findCustomer.response[0].id - response.method = "PATCH" - response.headers = $.getHeaders(.destination) - response + else: + name: updateCustomer + description: Build response for update customer + template: | + const response = $.defaultRequestConfig() + response.body.JSON = $.removeUndefinedAndNullValues($.context.payload.{~["id"]}) + response.endpoint = $.getEndpoint(.destination) + "/" + $.outputs.findCustomer.response[0].id + response.method = "PATCH" + response.headers = $.getHeaders(.destination) + response