diff --git a/.eslintrc.json b/.eslintrc.json
index c2614ac5ef6..d2928e50fdc 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -54,7 +54,7 @@
"@typescript-eslint/no-explicit-any": "off",
"class-methods-use-this": "off",
"@typescript-eslint/return-await": "error",
- "import/prefer-default-export": "error",
+ "import/prefer-default-export": "off",
"sonarjs/no-ignored-return": "error",
"no-new": "error",
"@typescript-eslint/no-shadow": "error",
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000000..b1edf4ef4f4
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,45 @@
+## What are the changes introduced in this PR?
+
+Write a brief explainer on your code changes.
+
+## Please explain the objectives of your changes below:
+
+Put down any required details on the broader aspect of your changes. If there are any dependent changes, **mandatorily** mention them here
+
+### Type of change
+
+If the pull request is a **bug-fix**, **enhancement** or a **refactor**, please fill in the details on the changes made.
+| Current changes | New changes |
+|--|--|
+| | |
+
+If the pull request is a **new feature**,
+
+### Any technical or performance related pointers to consider with the change?
+
+### Any new dependencies introduced with this change?
+
+Any new generic utility introduced or modified. Please explain the changes.
+
+### If the PR has changes in more than 5 files, please mention why the changes were not split into multiple PRs.
+
+--
+
+### If there are multiple linear items associated with the PR changes, please elaborate on the reason:
+
+--
+
+
+
+### Developer checklist
+
+[ ] **Please confirm if there are no breaking changes being introduced.**
+[ ] Are all related docs linked with the PR?
+[ ] Are all changes tested?
+[ ] Does this change require any RudderStack documentation changes?
+[ ] Are relevant unit and component test-cases added?
+
+### Reviewer checklist
+
+[ ] Is the PR tagged correctly as per the changes?
+[ ] Verified if there are no security credentials or confidential information in the changes.
diff --git a/package-lock.json b/package-lock.json
index 28d814c9258..4f83954e5b3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
"@ndhoule/extend": "^2.0.0",
"@pyroscope/nodejs": "^0.2.6",
"@rudderstack/integrations-lib": "^0.1.8",
- "@rudderstack/workflow-engine": "^0.5.7",
+ "@rudderstack/workflow-engine": "^0.6.9",
"ajv": "^8.12.0",
"ajv-draft-04": "^1.0.0",
"ajv-formats": "^2.1.1",
@@ -6657,13 +6657,45 @@
"tslib": "^2.4.0"
}
},
- "node_modules/@rudderstack/json-template-engine": {
+ "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",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.5.5.tgz",
+ "integrity": "sha512-p3HdTqgZiJjjZmjaHN2paa1e87ifGE5UjkA4zdvge4bBzJbKKMQNWqRg6I96SwoA+hsxNkW/f9R83SPLU9t7LA=="
},
- "node_modules/@rudderstack/workflow-engine": {
- "version": "0.5.7",
- "license": "MIT",
+ "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",
@@ -6673,27 +6705,81 @@
"object-sizeof": "^2.6.3"
}
},
+ "node_modules/@rudderstack/json-template-engine": {
+ "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.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",
+ "jsonata": "^2.0.3",
+ "lodash": "^4.17.21",
+ "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.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"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
"node_modules/@sideway/address": {
"version": "4.1.4",
@@ -19379,8 +19465,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",
@@ -19918,9 +20005,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 e4a4d9151c9..8a8b6177bdc 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"build:ci": "tsc -p tsconfig.json",
"build:swagger": "npm run build && npm run setup:swagger",
"build": "npm run build:ci && npm run copy",
+ "clean:build": "npm run clean && npm run build",
"build:clean": "npm run clean && npm run build",
"verify": "eslint . || exit 1; npm run test:js || exit 1",
"test:testRouter": "jest testRouter --detectOpenHandles --coverage --notify --watchAll=false",
@@ -64,7 +65,7 @@
"@ndhoule/extend": "^2.0.0",
"@pyroscope/nodejs": "^0.2.6",
"@rudderstack/integrations-lib": "^0.1.8",
- "@rudderstack/workflow-engine": "^0.5.7",
+ "@rudderstack/workflow-engine": "^0.6.9",
"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
new file mode 100644
index 00000000000..fe8697bc31a
--- /dev/null
+++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml
@@ -0,0 +1,88 @@
+bindings:
+ - name: EventType
+ path: ../../../../constants
+ - path: ./utils
+ exportAll: true
+ - name: defaultRequestConfig
+ path: ../../../../v0/util
+ - name: removeUndefinedAndNullValues
+ path: ../../../../v0/util
+ - name: getDestinationExternalID
+ path: ../../../../v0/util
+ - name: httpGET
+ path: ../../../../adapters/network
+ - name: processAxiosResponse
+ path: ../../../../adapters/utils/networkUtils
+
+
+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: preparePayload
+ template: |
+ $.context.payload = {
+ 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")
+ $.context.payload.phones = $.formatField(.message, "phone")
+ $.context.payload.customAttributes = $.getCustomAttributes(.message)
+ $.context.payload.externalCustomerId = $.getExternalCustomerId(.message)
+ $.context.payload.id = $.getCustomerId(.message)
+ $.context.payload = $.removeUndefinedAndNullValues($.context.payload)
+
+ - name: validatePayload
+ template: |
+ $.validatePayload($.context.payload)
+
+ - 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)
+ }
+ const endpoint = $.getEndpoint(.destination) + "?" + $.getQueryParams($.context.payload);
+ const rawResponse = await $.httpGET(endpoint,requestOptions)
+ const processedResponse = $.processAxiosResponse(rawResponse)
+ processedResponse
+
+ - name: createCustomer
+ description: Build response for create customer
+ 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)
+ 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
diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml
new file mode 100644
index 00000000000..341e5552c83
--- /dev/null
+++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml
@@ -0,0 +1,33 @@
+bindings:
+ - name: handleRtTfSingleEventError
+ path: ../../../../v0/util/index
+ - path: ./utils
+ exportAll: true
+
+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 00000000000..5abc9b6dd0f
--- /dev/null
+++ b/src/cdk/v2/destinations/gladly/utils.js
@@ -0,0 +1,175 @@
+const get = require('get-value');
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const {
+ base64Convertor,
+ getDestinationExternalID,
+} = require('../../../../v0/util');
+const { MappedToDestinationKey } = require('../../../../constants');
+
+const reservedCustomAttributes = [
+ 'email',
+ 'phone',
+ 'address',
+ 'name',
+ 'avatar',
+ 'firstName',
+ 'lastName',
+ 'userId',
+];
+
+const externalIdKey = 'context.externalId.0.id';
+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 ${base64Credentials}`,
+ };
+};
+
+const getEndpoint = (destination) => {
+ const { domain } = destination.Config;
+ return `https://${domain}/api/v1/customer-profiles`;
+};
+
+
+const getFieldValue = (field) => {
+ if (field) {
+ if (Array.isArray(field)) {
+ return field.map((item) => ({ original: item }));
+ }
+ return [{ original: 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) => {
+ const mappedToDestination = get(message, MappedToDestinationKey);
+ if (mappedToDestination) {
+ return formatFieldForRETl(message, fieldName);
+ }
+ return formatFieldForEventStream(message, fieldName);
+
+};
+
+const getCustomAttributes = (message) => {
+ const mappedToDestination = get(message, MappedToDestinationKey);
+ // for rETL
+ if (mappedToDestination) {
+ if (message?.traits?.customAttributes && typeof message.traits.customAttributes === 'object') {
+ return Object.keys(message.traits.customAttributes).length > 0 ? message.traits.customAttributes : undefined;
+ }
+ return undefined;
+ }
+
+ // for event stream
+ const customAttributes = message.context?.traits || {};
+ reservedCustomAttributes.forEach((customAttribute) => {
+ if (customAttributes[customAttribute]) {
+ delete customAttributes[customAttribute];
+ }
+ });
+ return Object.keys(customAttributes).length > 0 ? customAttributes : undefined;
+};
+
+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
+ return message.userId;
+};
+
+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
+ const customerId = getDestinationExternalID(message, 'GladlyCustomerId');
+ if (customerId) {
+ return customerId;
+ }
+
+ return undefined;
+};
+
+const validatePayload = (payload) => {
+ 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');
+ }
+};
+
+const getQueryParams = (payload) => {
+ if (payload.emails && payload.emails.length > 0) {
+ return `email=${encodeURIComponent(payload.emails[0].original)}`
+ }
+
+ 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,
+ getFieldValue,
+ getCustomerId,
+ getQueryParams,
+ validatePayload,
+ formatFieldForRETl,
+ getCustomAttributes,
+ 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 00000000000..116f150448f
--- /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 3058d62e51b..47d6d101797 100644
--- a/src/cdk/v2/handler.ts
+++ b/src/cdk/v2/handler.ts
@@ -64,8 +64,12 @@ export function getCachedWorkflowEngine(
return workflowEnginePromiseMap[destName][feature];
}
-export async function executeWorkflow(workflowEngine: WorkflowEngine, parsedEvent: FixMe) {
- const result = await workflowEngine.execute(parsedEvent);
+export async function executeWorkflow(
+ workflowEngine: WorkflowEngine,
+ parsedEvent: FixMe,
+ requestMetadata: NonNullable = {},
+) {
+ const result = await workflowEngine.execute(parsedEvent, { requestMetadata });
// TODO: Handle remaining output scenarios
return result.output;
}
@@ -74,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/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js
index d1e199c9e2d..48e7c6d8bb3 100644
--- a/src/constants/destinationCanonicalNames.js
+++ b/src/constants/destinationCanonicalNames.js
@@ -140,7 +140,7 @@ const DestCanonicalNames = {
'twitter_ads',
'TWITTER_ADS',
],
- BRAZE: ['BRAZE', 'Braze', 'braze'],
+ BRAZE: ['BRAZE', 'Braze', 'braze']
};
module.exports = { DestHandlerMap, DestCanonicalNames };
diff --git a/src/controllers/delivery.ts b/src/controllers/delivery.ts
index 5dee091c469..af94764d461 100644
--- a/src/controllers/delivery.ts
+++ b/src/controllers/delivery.ts
@@ -1,15 +1,15 @@
import { Context } from 'koa';
-import MiscService from '../services/misc';
+import { MiscService } from '../services/misc';
import { DeliveryResponse, ProcessorTransformationOutput } from '../types/index';
-import ServiceSelector from '../helpers/serviceSelector';
-import DeliveryTestService from '../services/delivertTest/deliveryTest';
-import ControllerUtility from './util';
+import { ServiceSelector } from '../helpers/serviceSelector';
+import { DeliveryTestService } from '../services/delivertTest/deliveryTest';
+import { ControllerUtility } from './util';
import logger from '../logger';
-import DestinationPostTransformationService from '../services/destination/postTransformation';
+import { DestinationPostTransformationService } from '../services/destination/postTransformation';
import tags from '../v0/util/tags';
import { FixMe } from '../util/types';
-export default class DeliveryController {
+export class DeliveryController {
public static async deliverToDestination(ctx: Context) {
logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body));
let deliveryResponse: DeliveryResponse;
diff --git a/src/controllers/destination.ts b/src/controllers/destination.ts
index 60eac8a56c4..71075d1b4ce 100644
--- a/src/controllers/destination.ts
+++ b/src/controllers/destination.ts
@@ -1,22 +1,22 @@
import { Context } from 'koa';
-import MiscService from '../services/misc';
-import PreTransformationDestinationService from '../services/destination/preTransformation';
-import PostTransformationDestinationService from '../services/destination/postTransformation';
+import { MiscService } from '../services/misc';
+import { DestinationPreTransformationService } from '../services/destination/preTransformation';
+import { DestinationPostTransformationService } from '../services/destination/postTransformation';
import {
ProcessorTransformationRequest,
RouterTransformationRequest,
ProcessorTransformationResponse,
RouterTransformationResponse,
} from '../types/index';
-import ServiceSelector from '../helpers/serviceSelector';
-import ControllerUtility from './util';
+import { ServiceSelector } from '../helpers/serviceSelector';
+import { ControllerUtility } from './util';
import stats from '../util/stats';
import logger from '../logger';
import { getIntegrationVersion } from '../util/utils';
import tags from '../v0/util/tags';
-import DynamicConfigParser from '../util/dynamicConfigParser';
+import { DynamicConfigParser } from '../util/dynamicConfigParser';
-export default class DestinationController {
+export class DestinationController {
public static async destinationTransformAtProcessor(ctx: Context) {
const startTime = new Date();
logger.debug(
@@ -36,7 +36,7 @@ export default class DestinationController {
const integrationService = ServiceSelector.getDestinationService(events);
try {
integrationService.init();
- events = PreTransformationDestinationService.preProcess(
+ events = DestinationPreTransformationService.preProcess(
events,
ctx,
) as ProcessorTransformationRequest[];
@@ -59,7 +59,7 @@ export default class DestinationController {
tags.FEATURES.PROCESSOR,
);
metaTO.metadata = ev.metadata;
- const errResp = PostTransformationDestinationService.handleProcessorTransformFailureEvents(
+ const errResp = DestinationPostTransformationService.handleProcessorTransformFailureEvents(
error,
metaTO,
);
@@ -110,7 +110,7 @@ export default class DestinationController {
const integrationService = ServiceSelector.getDestinationService(events);
let resplist: RouterTransformationResponse[];
try {
- events = PreTransformationDestinationService.preProcess(events, ctx);
+ events = DestinationPreTransformationService.preProcess(events, ctx);
const timestampCorrectEvents = ControllerUtility.handleTimestampInEvents(events);
events = DynamicConfigParser.process(timestampCorrectEvents);
resplist = await integrationService.doRouterTransformation(
@@ -127,7 +127,7 @@ export default class DestinationController {
tags.FEATURES.ROUTER,
);
metaTO.metadatas = events.map((ev) => ev.metadata);
- const errResp = PostTransformationDestinationService.handleRouterTransformFailureEvents(
+ const errResp = DestinationPostTransformationService.handleRouterTransformFailureEvents(
error,
metaTO,
);
@@ -165,7 +165,7 @@ export default class DestinationController {
let events = routerRequest.input;
const integrationService = ServiceSelector.getDestinationService(events);
try {
- events = PreTransformationDestinationService.preProcess(events, ctx);
+ events = DestinationPreTransformationService.preProcess(events, ctx);
const timestampCorrectEvents = ControllerUtility.handleTimestampInEvents(events);
const resplist = integrationService.doBatchTransformation(
timestampCorrectEvents,
@@ -182,7 +182,7 @@ export default class DestinationController {
tags.FEATURES.BATCH,
);
metaTO.metadatas = events.map((ev) => ev.metadata);
- const errResp = PostTransformationDestinationService.handleBatchTransformFailureEvents(
+ const errResp = DestinationPostTransformationService.handleBatchTransformFailureEvents(
error,
metaTO,
);
diff --git a/src/controllers/eventTest.ts b/src/controllers/eventTest.ts
index 8888b638075..d5507f65d29 100644
--- a/src/controllers/eventTest.ts
+++ b/src/controllers/eventTest.ts
@@ -1,9 +1,9 @@
import { Context } from 'koa';
-import EventTesterService from '../services/eventTest/eventTester';
+import { EventTesterService } from '../services/eventTest/eventTester';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { CatchErr, FixMe } from '../util/types';
-export default class EventTestController {
+export class EventTestController {
private static API_VERSION = '1';
public static async testEvent(ctx: Context) {
diff --git a/src/controllers/misc.ts b/src/controllers/misc.ts
index 92ec33f80f3..e2efdab5db7 100644
--- a/src/controllers/misc.ts
+++ b/src/controllers/misc.ts
@@ -1,7 +1,7 @@
import { Context } from 'koa';
-import MiscService from '../services/misc';
+import { MiscService } from '../services/misc';
-export default class MiscController {
+export class MiscController {
public static healthStats(ctx: Context) {
ctx.body = MiscService.getHealthInfo();
ctx.status = 200;
diff --git a/src/controllers/profile.ts b/src/controllers/profile.ts
index 984f4ac6457..a0a62bf4792 100644
--- a/src/controllers/profile.ts
+++ b/src/controllers/profile.ts
@@ -1,8 +1,8 @@
import { Context } from 'koa';
-import ProfileService from '../services/profile';
-import ControllerUtility from './util';
+import { ProfileService } from '../services/profile';
+import { ControllerUtility } from './util';
-export default class ProfileController {
+export class ProfileController {
/**
* Example usage of API
*
diff --git a/src/controllers/regulation.ts b/src/controllers/regulation.ts
index cefe1f748ad..a50541780d6 100644
--- a/src/controllers/regulation.ts
+++ b/src/controllers/regulation.ts
@@ -1,15 +1,14 @@
import { Context } from 'koa';
import logger from '../logger';
import { UserDeletionRequest, UserDeletionResponse } from '../types';
-import ServiceSelector from '../helpers/serviceSelector';
+import { ServiceSelector } from '../helpers/serviceSelector';
import tags from '../v0/util/tags';
import stats from '../util/stats';
-import PostTransformationDestinationService from '../services/destination/postTransformation';
+import { DestinationPostTransformationService } from '../services/destination/postTransformation';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { CatchErr } from '../util/types';
-// TODO: refactor this class to new format
-export default class RegulationController {
+export class RegulationController {
public static async deleteUsers(ctx: Context) {
logger.debug(
'Native(Process-Transform):: Requst to transformer::',
@@ -44,7 +43,7 @@ export default class RegulationController {
tags.FEATURES.USER_DELETION,
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- const errResp = PostTransformationDestinationService.handleUserDeletionFailureEvents(
+ const errResp = DestinationPostTransformationService.handleUserDeletionFailureEvents(
error,
metaTO,
);
diff --git a/src/controllers/source.ts b/src/controllers/source.ts
index b1e7e8fe129..ef5483a756f 100644
--- a/src/controllers/source.ts
+++ b/src/controllers/source.ts
@@ -1,11 +1,11 @@
import { Context } from 'koa';
-import MiscService from '../services/misc';
-import ServiceSelector from '../helpers/serviceSelector';
-import ControllerUtility from './util';
+import { MiscService } from '../services/misc';
+import { ServiceSelector } from '../helpers/serviceSelector';
+import { ControllerUtility } from './util';
import logger from '../logger';
-import PostTransformationServiceSource from '../services/source/postTransformation';
+import { SourcePostTransformationService } from '../services/source/postTransformation';
-export default class SourceController {
+export class SourceController {
public static async sourceTransform(ctx: Context) {
logger.debug(
'Native(Source-Transform):: Request to transformer::',
@@ -30,7 +30,7 @@ export default class SourceController {
ctx.body = resplist;
} catch (err: any) {
const metaTO = integrationService.getTags();
- const resp = PostTransformationServiceSource.handleFailureEventsSource(err, metaTO);
+ const resp = SourcePostTransformationService.handleFailureEventsSource(err, metaTO);
ctx.body = [resp];
}
ControllerUtility.postProcess(ctx);
diff --git a/src/controllers/trackingPlan.ts b/src/controllers/trackingPlan.ts
index 66bf3607003..74e47e0ec92 100644
--- a/src/controllers/trackingPlan.ts
+++ b/src/controllers/trackingPlan.ts
@@ -1,8 +1,8 @@
import { Context } from 'koa';
-import TrackingPlanservice from '../services/trackingPlan';
-import ControllerUtility from './util';
+import { TrackingPlanservice } from '../services/trackingPlan';
+import { ControllerUtility } from './util';
-export default class TrackingPlanController {
+export class TrackingPlanController {
public static async validateTrackingPlan(ctx: Context) {
const events = ctx.request.body;
const requestSize = Number(ctx.request.get('content-length'));
diff --git a/src/controllers/userTransform.ts b/src/controllers/userTransform.ts
index 6cbf5780778..c344bd072ad 100644
--- a/src/controllers/userTransform.ts
+++ b/src/controllers/userTransform.ts
@@ -1,15 +1,15 @@
import { Context } from 'koa';
import { ProcessorTransformationRequest, UserTransformationServiceResponse } from '../types/index';
-import UserTransformService from '../services/userTransform';
+import { UserTransformService } from '../services/userTransform';
import logger from '../logger';
import {
setupUserTransformHandler,
extractLibraries,
validateCode,
} from '../util/customTransformer';
-import ControllerUtility from './util';
+import { ControllerUtility } from './util';
-export default class UserTransformController {
+export class UserTransformController {
public static async transform(ctx: Context) {
logger.debug(
'(User transform - router:/customTransform ):: Request to transformer',
diff --git a/src/controllers/util/index.test.ts b/src/controllers/util/index.test.ts
index 0bc5f174b07..e23d3f68324 100644
--- a/src/controllers/util/index.test.ts
+++ b/src/controllers/util/index.test.ts
@@ -1,4 +1,4 @@
-import ControllerUtility from './index';
+import { ControllerUtility } from './index';
describe('adaptInputToVersion', () => {
it('should return the input unchanged when the implementation version is not found', () => {
diff --git a/src/controllers/util/index.ts b/src/controllers/util/index.ts
index c52eb6899e1..75d3d8ffa7c 100644
--- a/src/controllers/util/index.ts
+++ b/src/controllers/util/index.ts
@@ -15,7 +15,7 @@ import { getValueFromMessage } from '../../v0/util';
import genericFieldMap from '../../v0/util/data/GenericFieldMapping.json';
import { EventType, MappedToDestinationKey } from '../../constants';
-export default class ControllerUtility {
+export class ControllerUtility {
private static sourceVersionMap: Map = new Map();
public static timestampValsMap: Record = {
diff --git a/src/features.json b/src/features.json
index 27da6cfaf2c..224968c99b7 100644
--- a/src/features.json
+++ b/src/features.json
@@ -60,6 +60,7 @@
"TWITTER_ADS": true,
"CLEVERTAP": true,
"ORTTO": true,
+ "GLADLY": true,
"ONE_SIGNAL": true,
"TIKTOK_AUDIENCE": true
},
diff --git a/src/helpers/fetchHandlers.ts b/src/helpers/fetchHandlers.ts
index 96a74e528d9..ef7d9e66116 100644
--- a/src/helpers/fetchHandlers.ts
+++ b/src/helpers/fetchHandlers.ts
@@ -1,6 +1,6 @@
-import MiscService from '../services/misc';
+import { MiscService } from '../services/misc';
-export default class FetchHandler {
+export class FetchHandler {
private static sourceHandlerMap: Map = new Map();
private static destHandlerMap: Map = new Map();
diff --git a/src/helpers/serviceSelector.ts b/src/helpers/serviceSelector.ts
index 891c21313da..89678e94074 100644
--- a/src/helpers/serviceSelector.ts
+++ b/src/helpers/serviceSelector.ts
@@ -1,16 +1,16 @@
import { PlatformError } from '@rudderstack/integrations-lib';
import { ProcessorTransformationRequest, RouterTransformationRequestData } from '../types/index';
import { INTEGRATION_SERVICE } from '../routes/utils/constants';
-import CDKV1DestinationService from '../services/destination/cdkV1Integration';
-import CDKV2DestinationService from '../services/destination/cdkV2Integration';
-import DestinationService from '../interfaces/DestinationService';
-import NativeIntegrationDestinationService from '../services/destination/nativeIntegration';
-import SourceService from '../interfaces/SourceService';
-import NativeIntegrationSourceService from '../services/source/nativeIntegration';
-import ComparatorService from '../services/comparator';
+import { CDKV1DestinationService } from '../services/destination/cdkV1Integration';
+import { CDKV2DestinationService } from '../services/destination/cdkV2Integration';
+import { DestinationService } from '../interfaces/DestinationService';
+import { NativeIntegrationDestinationService } from '../services/destination/nativeIntegration';
+import { SourceService } from '../interfaces/SourceService';
+import { NativeIntegrationSourceService } from '../services/source/nativeIntegration';
+import { ComparatorService } from '../services/comparator';
import { FixMe } from '../util/types';
-export default class ServiceSelector {
+export class ServiceSelector {
private static serviceMap: Map = new Map();
private static services = {
diff --git a/src/index.ts b/src/index.ts
index d1cc95cc360..36f32f1aed6 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,8 +4,6 @@ import gracefulShutdown from 'http-graceful-shutdown';
import dotenv from 'dotenv';
import logger from './logger';
import cluster from './util/cluster';
-import { router } from './legacy/router';
-import { testRouter } from './testRouter';
import { metricsRouter } from './routes/metricsRouter';
import { addStatMiddleware, addRequestSizeMiddleware, initPyroscope } from './middleware';
import { logProcessInfo } from './util/utils';
@@ -14,7 +12,6 @@ import { RedisDB } from './util/redis/redisConnector';
dotenv.config();
const clusterEnabled = process.env.CLUSTER_ENABLED !== 'false';
-const useUpdatedRoutes = process.env.ENABLE_NEW_ROUTES !== 'false';
const port = parseInt(process.env.PORT ?? '9090', 10);
const metricsPort = parseInt(process.env.METRICS_PORT || '9091', 10);
@@ -35,15 +32,8 @@ app.use(
addRequestSizeMiddleware(app);
addSwaggerRoutes(app);
-if (useUpdatedRoutes) {
- logger.info('Using new routes');
- applicationRoutes(app);
-} else {
- // To be depricated
- logger.info('Using old routes');
- app.use(router.routes()).use(router.allowedMethods());
- app.use(testRouter.routes()).use(testRouter.allowedMethods());
-}
+logger.info('Using new routes');
+applicationRoutes(app);
function finalFunction() {
RedisDB.disconnect();
diff --git a/src/interfaces/DestinationService.ts b/src/interfaces/DestinationService.ts
index 123da35292f..b27f98da2a0 100644
--- a/src/interfaces/DestinationService.ts
+++ b/src/interfaces/DestinationService.ts
@@ -10,7 +10,7 @@ import {
UserDeletionResponse,
} from '../types/index';
-export default interface DestinationService {
+export interface DestinationService {
getName(): string;
init(): void;
diff --git a/src/interfaces/SourceService.ts b/src/interfaces/SourceService.ts
index 99b29f095bd..c7de8cfe8bc 100644
--- a/src/interfaces/SourceService.ts
+++ b/src/interfaces/SourceService.ts
@@ -1,6 +1,6 @@
import { MetaTransferObject, SourceTransformationResponse } from '../types/index';
-export default interface SourceService {
+export interface SourceService {
getTags(): MetaTransferObject;
sourceTransformRoutine(
diff --git a/src/middlewares/featureFlag.ts b/src/middlewares/featureFlag.ts
index 146e57186c3..206fa7c5ea1 100644
--- a/src/middlewares/featureFlag.ts
+++ b/src/middlewares/featureFlag.ts
@@ -6,7 +6,7 @@ export interface FeatureFlags {
export const FEATURE_FILTER_CODE = 'filter-code';
-export default class FeatureFlagMiddleware {
+export class FeatureFlagMiddleware {
public static async handle(ctx: Context, next: Next): Promise {
// Initialize ctx.state.features if it doesn't exist
ctx.state.features = (ctx.state.features || {}) as FeatureFlags;
diff --git a/src/middlewares/routeActivation.ts b/src/middlewares/routeActivation.ts
index dfdaef964ee..ffb1e15e80b 100644
--- a/src/middlewares/routeActivation.ts
+++ b/src/middlewares/routeActivation.ts
@@ -18,7 +18,7 @@ const destinationFilterList = process.env.DESTINATION_FILTER_LIST?.toLocaleLower
const sourceFilteList = process.env.SOURCE_FILTER_LIST?.toLocaleLowerCase();
const deliveryFilterList = process.env.DESTINATION_DELIVERY_FILTER_LIST?.toLocaleLowerCase();
-export default class RouteActivationMiddleware {
+export class RouteActivationMiddleware {
private static executeActivationRule(ctx: Context, next: Next, shouldActivate: boolean) {
if (shouldActivate) {
return next();
diff --git a/src/routes/delivery.ts b/src/routes/delivery.ts
index 141700fd9fb..0591dc8b9eb 100644
--- a/src/routes/delivery.ts
+++ b/src/routes/delivery.ts
@@ -1,6 +1,6 @@
import Router from '@koa/router';
-import DeliveryController from '../controllers/delivery';
-import RouteActivationMiddleware from '../middlewares/routeActivation';
+import { DeliveryController } from '../controllers/delivery';
+import { RouteActivationMiddleware } from '../middlewares/routeActivation';
const router = new Router();
diff --git a/src/routes/destination.ts b/src/routes/destination.ts
index 3d4be42ff3c..1c478251455 100644
--- a/src/routes/destination.ts
+++ b/src/routes/destination.ts
@@ -1,30 +1,30 @@
import Router from '@koa/router';
-import DestinationController from '../controllers/destination';
-import RegulationController from '../controllers/regulation';
-import FeatureFlagController from '../middlewares/featureFlag';
-import RouteActivationController from '../middlewares/routeActivation';
+import { DestinationController } from '../controllers/destination';
+import { RegulationController } from '../controllers/regulation';
+import { FeatureFlagMiddleware } from '../middlewares/featureFlag';
+import { RouteActivationMiddleware } from '../middlewares/routeActivation';
const router = new Router();
router.post(
'/:version/destinations/:destination',
- RouteActivationController.isDestinationRouteActive,
- RouteActivationController.destinationProcFilter,
- FeatureFlagController.handle,
+ RouteActivationMiddleware.isDestinationRouteActive,
+ RouteActivationMiddleware.destinationProcFilter,
+ FeatureFlagMiddleware.handle,
DestinationController.destinationTransformAtProcessor,
);
router.post(
'/routerTransform',
- RouteActivationController.isDestinationRouteActive,
- RouteActivationController.destinationRtFilter,
- FeatureFlagController.handle,
+ RouteActivationMiddleware.isDestinationRouteActive,
+ RouteActivationMiddleware.destinationRtFilter,
+ FeatureFlagMiddleware.handle,
DestinationController.destinationTransformAtRouter,
);
router.post(
'/batch',
- RouteActivationController.isDestinationRouteActive,
- RouteActivationController.destinationBatchFilter,
- FeatureFlagController.handle,
+ RouteActivationMiddleware.isDestinationRouteActive,
+ RouteActivationMiddleware.destinationBatchFilter,
+ FeatureFlagMiddleware.handle,
DestinationController.batchProcess,
);
diff --git a/src/routes/misc.ts b/src/routes/misc.ts
index 750c1194dd3..4d1c5d5fb54 100644
--- a/src/routes/misc.ts
+++ b/src/routes/misc.ts
@@ -1,6 +1,6 @@
import Router from '@koa/router';
-import ProfileController from '../controllers/profile';
-import MiscController from '../controllers/misc';
+import { ProfileController } from '../controllers/profile';
+import { MiscController } from '../controllers/misc';
const router = new Router();
diff --git a/src/routes/source.ts b/src/routes/source.ts
index ade26c8700f..1abc46e2e4d 100644
--- a/src/routes/source.ts
+++ b/src/routes/source.ts
@@ -1,13 +1,13 @@
import Router from '@koa/router';
-import RouteActivationController from '../middlewares/routeActivation';
-import SourceController from '../controllers/source';
+import { RouteActivationMiddleware } from '../middlewares/routeActivation';
+import { SourceController } from '../controllers/source';
const router = new Router();
router.post(
'/:version/sources/:source',
- RouteActivationController.isSourceRouteActive,
- RouteActivationController.sourceFilter,
+ RouteActivationMiddleware.isSourceRouteActive,
+ RouteActivationMiddleware.sourceFilter,
SourceController.sourceTransform,
);
diff --git a/src/routes/testEvents.ts b/src/routes/testEvents.ts
index 556ec10198b..a35fe447ba7 100644
--- a/src/routes/testEvents.ts
+++ b/src/routes/testEvents.ts
@@ -1,5 +1,5 @@
import Router from '@koa/router';
-import EventTestController from '../controllers/eventTest';
+import { EventTestController } from '../controllers/eventTest';
const router = new Router({ prefix: '/test-router' });
diff --git a/src/routes/trackingPlan.ts b/src/routes/trackingPlan.ts
index d177af7b2ce..3e62ba2a749 100644
--- a/src/routes/trackingPlan.ts
+++ b/src/routes/trackingPlan.ts
@@ -1,5 +1,5 @@
import Router from '@koa/router';
-import TrackingPlanController from '../controllers/trackingPlan';
+import { TrackingPlanController } from '../controllers/trackingPlan';
const router = new Router();
diff --git a/src/routes/userTransform.ts b/src/routes/userTransform.ts
index 23870db3b4b..1fb8ad3a1cd 100644
--- a/src/routes/userTransform.ts
+++ b/src/routes/userTransform.ts
@@ -1,34 +1,34 @@
import Router from '@koa/router';
-import RouteActivationController from '../middlewares/routeActivation';
-import FeatureFlagController from '../middlewares/featureFlag';
-import UserTransformController from '../controllers/userTransform';
+import { RouteActivationMiddleware } from '../middlewares/routeActivation';
+import { FeatureFlagMiddleware } from '../middlewares/featureFlag';
+import { UserTransformController } from '../controllers/userTransform';
const router = new Router();
router.post(
'/customTransform',
- RouteActivationController.isUserTransformRouteActive,
- FeatureFlagController.handle,
+ RouteActivationMiddleware.isUserTransformRouteActive,
+ FeatureFlagMiddleware.handle,
UserTransformController.transform,
);
router.post(
'/transformation/test',
- RouteActivationController.isUserTransformTestRouteActive,
+ RouteActivationMiddleware.isUserTransformTestRouteActive,
UserTransformController.testTransform,
);
router.post(
'/transformationLibrary/test',
- RouteActivationController.isUserTransformTestRouteActive,
+ RouteActivationMiddleware.isUserTransformTestRouteActive,
UserTransformController.testTransformLibrary,
);
router.post(
'/transformation/sethandle',
- RouteActivationController.isUserTransformTestRouteActive,
+ RouteActivationMiddleware.isUserTransformTestRouteActive,
UserTransformController.testTransformSethandle,
);
router.post(
'/extractLibs',
- RouteActivationController.isUserTransformRouteActive,
+ RouteActivationMiddleware.isUserTransformRouteActive,
UserTransformController.extractLibhandle,
);
diff --git a/src/services/comparator.ts b/src/services/comparator.ts
index 3495b7bbfd0..7f63da94022 100644
--- a/src/services/comparator.ts
+++ b/src/services/comparator.ts
@@ -1,5 +1,5 @@
/* eslint-disable class-methods-use-this */
-import IntegrationDestinationService from '../interfaces/DestinationService';
+import { DestinationService } from '../interfaces/DestinationService';
import {
DeliveryResponse,
Destination,
@@ -20,15 +20,12 @@ import { CommonUtils } from '../util/common';
const NS_PER_SEC = 1e9;
-export default class ComparatorService implements IntegrationDestinationService {
- secondaryService: IntegrationDestinationService;
+export class ComparatorService implements DestinationService {
+ secondaryService: DestinationService;
- primaryService: IntegrationDestinationService;
+ primaryService: DestinationService;
- constructor(
- primaryService: IntegrationDestinationService,
- secondaryService: IntegrationDestinationService,
- ) {
+ constructor(primaryService: DestinationService, secondaryService: DestinationService) {
this.primaryService = primaryService;
this.secondaryService = secondaryService;
}
diff --git a/src/services/delivertTest/deliveryTest.ts b/src/services/delivertTest/deliveryTest.ts
index e5713073c15..0d960ade172 100644
--- a/src/services/delivertTest/deliveryTest.ts
+++ b/src/services/delivertTest/deliveryTest.ts
@@ -7,7 +7,7 @@ import stats from '../../util/stats';
import logger from '../../logger';
import tags from '../../v0/util/tags';
-export default class DeliveryTestService {
+export class DeliveryTestService {
public static async doTestDelivery(
destination: string,
routerDestReqPayload: any,
diff --git a/src/services/destination/cdkV1Integration.ts b/src/services/destination/cdkV1Integration.ts
index 2f655fca9b9..8ccd3341e59 100644
--- a/src/services/destination/cdkV1Integration.ts
+++ b/src/services/destination/cdkV1Integration.ts
@@ -2,7 +2,7 @@
import { ConfigFactory, Executor, RudderBaseConfig } from 'rudder-transformer-cdk';
import path from 'path';
import { TransformationError } from '@rudderstack/integrations-lib';
-import IntegrationDestinationService from '../../interfaces/DestinationService';
+import { DestinationService } from '../../interfaces/DestinationService';
import {
DeliveryResponse,
ErrorDetailer,
@@ -15,12 +15,12 @@ import {
UserDeletionRequest,
UserDeletionResponse,
} from '../../types/index';
-import DestinationPostTransformationService from './postTransformation';
+import { DestinationPostTransformationService } from './postTransformation';
import tags from '../../v0/util/tags';
import { getErrorInfo } from '../../cdk/v1/handler';
import { CatchErr } from '../../util/types';
-export default class CDKV1DestinationService implements IntegrationDestinationService {
+export class CDKV1DestinationService implements DestinationService {
public init() {
ConfigFactory.init({
basePath: path.resolve(__dirname, '../../cdk/v1'),
diff --git a/src/services/destination/cdkV2Integration.ts b/src/services/destination/cdkV2Integration.ts
index bc5a1126a89..f3be2c01443 100644
--- a/src/services/destination/cdkV2Integration.ts
+++ b/src/services/destination/cdkV2Integration.ts
@@ -3,7 +3,7 @@
import groupBy from 'lodash/groupBy';
import { TransformationError } from '@rudderstack/integrations-lib';
import { processCdkV2Workflow } from '../../cdk/v2/handler';
-import IntegrationDestinationService from '../../interfaces/DestinationService';
+import { DestinationService } from '../../interfaces/DestinationService';
import {
DeliveryResponse,
ErrorDetailer,
@@ -17,11 +17,11 @@ import {
UserDeletionResponse,
} from '../../types/index';
import tags from '../../v0/util/tags';
-import DestinationPostTransformationService from './postTransformation';
+import { DestinationPostTransformationService } from './postTransformation';
import stats from '../../util/stats';
import { CatchErr } from '../../util/types';
-export default class CDKV2DestinationService implements IntegrationDestinationService {
+export class CDKV2DestinationService implements DestinationService {
public init() {}
public getName(): string {
@@ -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,
diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts
index a788b388e4b..6763f54c7e0 100644
--- a/src/services/destination/nativeIntegration.ts
+++ b/src/services/destination/nativeIntegration.ts
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import groupBy from 'lodash/groupBy';
import cloneDeep from 'lodash/cloneDeep';
-import IntegrationDestinationService from '../../interfaces/DestinationService';
+import { DestinationService } from '../../interfaces/DestinationService';
import {
DeliveryResponse,
ErrorDetailer,
@@ -14,12 +14,12 @@ import {
UserDeletionRequest,
UserDeletionResponse,
} from '../../types/index';
-import DestinationPostTransformationService from './postTransformation';
+import { DestinationPostTransformationService } from './postTransformation';
import networkHandlerFactory from '../../adapters/networkHandlerFactory';
-import FetchHandler from '../../helpers/fetchHandlers';
+import { FetchHandler } from '../../helpers/fetchHandlers';
import tags from '../../v0/util/tags';
-export default class NativeIntegrationDestinationService implements IntegrationDestinationService {
+export class NativeIntegrationDestinationService implements DestinationService {
public init() {}
public getName(): string {
diff --git a/src/services/destination/postTransformation.ts b/src/services/destination/postTransformation.ts
index 090e39b0590..0b91eb7cc19 100644
--- a/src/services/destination/postTransformation.ts
+++ b/src/services/destination/postTransformation.ts
@@ -12,11 +12,11 @@ import {
UserDeletionResponse,
} from '../../types/index';
import { generateErrorObject } from '../../v0/util';
-import ErrorReportingService from '../errorReporting';
+import { ErrorReportingService } from '../errorReporting';
import tags from '../../v0/util/tags';
import stats from '../../util/stats';
-export default class DestinationPostTransformationService {
+export class DestinationPostTransformationService {
public static handleProcessorTransformSucessEvents(
event: ProcessorTransformationRequest,
transformedPayloads: ProcessorTransformationOutput | ProcessorTransformationOutput[],
diff --git a/src/services/destination/preTransformation.ts b/src/services/destination/preTransformation.ts
index dd5579b7b9f..db1d76d64ed 100644
--- a/src/services/destination/preTransformation.ts
+++ b/src/services/destination/preTransformation.ts
@@ -1,7 +1,7 @@
import { Context } from 'koa';
import { ProcessorTransformationRequest, RouterTransformationRequestData } from '../../types/index';
-export default class PreTransformationDestinationService {
+export class DestinationPreTransformationService {
public static preProcess(
events: ProcessorTransformationRequest[] | RouterTransformationRequestData[],
ctx: Context,
diff --git a/src/services/errorReporting.ts b/src/services/errorReporting.ts
index 2d3c84ff30b..3a4276f9780 100644
--- a/src/services/errorReporting.ts
+++ b/src/services/errorReporting.ts
@@ -1,6 +1,6 @@
import { client } from '../util/errorNotifier';
-export default class ErrorReportingService {
+export class ErrorReportingService {
public static reportError(error: NonNullable, context: string, errorResp: object) {
client.notify(error, context, {
...errorResp,
diff --git a/src/services/eventTest/eventTester.ts b/src/services/eventTest/eventTester.ts
index a3755d3e803..75df29cc63c 100644
--- a/src/services/eventTest/eventTester.ts
+++ b/src/services/eventTest/eventTester.ts
@@ -1,7 +1,7 @@
import { sendToDestination, userTransformHandler } from '../../routerUtils';
import { FixMe } from '../../util/types';
-export default class EventTesterService {
+export class EventTesterService {
private static getDestHandler(version, destination) {
// eslint-disable-next-line global-require, import/no-dynamic-require
return require(`../../${version}/destinations/${destination}/transform`);
diff --git a/src/services/misc.ts b/src/services/misc.ts
index 4c3e2ae6dae..e0953d08bf1 100644
--- a/src/services/misc.ts
+++ b/src/services/misc.ts
@@ -6,7 +6,7 @@ import { DestHandlerMap } from '../constants/destinationCanonicalNames';
import { Metadata } from '../types';
import { getCPUProfile, getHeapProfile } from '../middleware';
-export default class MiscService {
+export class MiscService {
public static getDestHandler(dest: string, version: string) {
if (DestHandlerMap.hasOwnProperty(dest)) {
return require(`../${version}/destinations/${DestHandlerMap[dest]}/transform`);
diff --git a/src/services/profile.ts b/src/services/profile.ts
index 4d7a7104e7a..d71826e251e 100644
--- a/src/services/profile.ts
+++ b/src/services/profile.ts
@@ -22,7 +22,7 @@ logger.info(`Interval Bytes set: ${intervalBytes}`);
heap.start(intervalBytes, stackDepth);
-export default class ProfileService {
+export class ProfileService {
private static async promisifiedRead(readable: any) {
// eslint-disable-next-line no-new
new Promise((resolve, reject) => {
diff --git a/src/services/source/nativeIntegration.ts b/src/services/source/nativeIntegration.ts
index 22783db049c..6eaef2f835b 100644
--- a/src/services/source/nativeIntegration.ts
+++ b/src/services/source/nativeIntegration.ts
@@ -1,4 +1,4 @@
-import IntegrationSourceService from '../../interfaces/SourceService';
+import { SourceService } from '../../interfaces/SourceService';
import {
ErrorDetailer,
MetaTransferObject,
@@ -6,12 +6,12 @@ import {
SourceTransformationResponse,
} from '../../types/index';
import { FixMe } from '../../util/types';
-import PostTransformationServiceSource from './postTransformation';
-import FetchHandler from '../../helpers/fetchHandlers';
+import { SourcePostTransformationService } from './postTransformation';
+import { FetchHandler } from '../../helpers/fetchHandlers';
import tags from '../../v0/util/tags';
import stats from '../../util/stats';
-export default class NativeIntegrationSourceService implements IntegrationSourceService {
+export class NativeIntegrationSourceService implements SourceService {
public getTags(): MetaTransferObject {
const metaTO = {
errorDetails: {
@@ -38,14 +38,14 @@ export default class NativeIntegrationSourceService implements IntegrationSource
try {
const respEvents: RudderMessage | RudderMessage[] | SourceTransformationResponse =
await sourceHandler.process(sourceEvent);
- return PostTransformationServiceSource.handleSuccessEventsSource(respEvents);
+ return SourcePostTransformationService.handleSuccessEventsSource(respEvents);
} catch (error: FixMe) {
const metaTO = this.getTags();
stats.increment('source_transform_errors', {
source: sourceType,
version,
});
- return PostTransformationServiceSource.handleFailureEventsSource(error, metaTO);
+ return SourcePostTransformationService.handleFailureEventsSource(error, metaTO);
}
}),
);
diff --git a/src/services/source/postTransformation.ts b/src/services/source/postTransformation.ts
index f732cac3a77..20c815171be 100644
--- a/src/services/source/postTransformation.ts
+++ b/src/services/source/postTransformation.ts
@@ -1,9 +1,9 @@
import { MetaTransferObject, RudderMessage, SourceTransformationResponse } from '../../types/index';
import { CatchErr } from '../../util/types';
import { generateErrorObject } from '../../v0/util';
-import ErrorReportingService from '../errorReporting';
+import { ErrorReportingService } from '../errorReporting';
-export default class PostTransformationSourceService {
+export class SourcePostTransformationService {
public static handleFailureEventsSource(
error: CatchErr,
metaTO: MetaTransferObject,
diff --git a/src/services/trackingPlan.ts b/src/services/trackingPlan.ts
index 35f21320a57..2e68df55e99 100644
--- a/src/services/trackingPlan.ts
+++ b/src/services/trackingPlan.ts
@@ -4,7 +4,7 @@ import { getMetadata } from '../v0/util';
import eventValidator from '../util/eventValidation';
import stats from '../util/stats';
-export default class TrackingPlanservice {
+export class TrackingPlanservice {
public static async validateTrackingPlan(events, requestSize, reqParams) {
const requestStartTime = new Date();
const respList: any[] = [];
diff --git a/src/services/userTransform.ts b/src/services/userTransform.ts
index 4fe1ad0b52a..ddd5c82f67b 100644
--- a/src/services/userTransform.ts
+++ b/src/services/userTransform.ts
@@ -24,7 +24,7 @@ import { CatchErr, FixMe } from '../util/types';
import { FeatureFlags, FEATURE_FILTER_CODE } from '../middlewares/featureFlag';
import { HTTP_CUSTOM_STATUS_CODES } from '../constants';
-export default class UserTransformService {
+export class UserTransformService {
public static async transformRoutine(
events: ProcessorTransformationRequest[],
features: FeatureFlags = {},
diff --git a/src/util/dynamicConfigParser.ts b/src/util/dynamicConfigParser.ts
index 73ad85a0d2c..6de2e38f945 100644
--- a/src/util/dynamicConfigParser.ts
+++ b/src/util/dynamicConfigParser.ts
@@ -6,7 +6,7 @@ import { FixMe } from './types';
const get = require('get-value');
const unset = require('unset-value');
-export default class DynamicConfigParser {
+export class DynamicConfigParser {
private static getDynamicConfigValue(
event: ProcessorTransformationRequest | RouterTransformationRequestData,
value: FixMe,
diff --git a/src/util/openfaas/index.js b/src/util/openfaas/index.js
index 47a69aeb186..2792003f4ac 100644
--- a/src/util/openfaas/index.js
+++ b/src/util/openfaas/index.js
@@ -277,7 +277,7 @@ const executeFaasFunction = async (
}
if (error.statusCode === 504) {
- throw new RespStatusError(`${name} timed out`);
+ throw new RespStatusError(`${name} timed out`, 504);
}
throw error;
diff --git a/src/v0/destinations/hs/util.js b/src/v0/destinations/hs/util.js
index 72025997d20..f83ce0b6de7 100644
--- a/src/v0/destinations/hs/util.js
+++ b/src/v0/destinations/hs/util.js
@@ -607,7 +607,8 @@ const splitEventsForCreateUpdate = async (inputs, destination) => {
const { destinationExternalId } = getDestinationExternalIDInfoForRetl(message, DESTINATION);
const filteredInfo = updateHubspotIds.filter(
- (update) => update.property.toString().toLowerCase() === destinationExternalId.toString().toLowerCase(),
+ (update) =>
+ update.property.toString().toLowerCase() === destinationExternalId.toString().toLowerCase(),
);
if (filteredInfo.length > 0) {
diff --git a/src/v0/destinations/mp/config.js b/src/v0/destinations/mp/config.js
index 41a801e9da1..35b40294f55 100644
--- a/src/v0/destinations/mp/config.js
+++ b/src/v0/destinations/mp/config.js
@@ -11,6 +11,9 @@ const ConfigCategory = {
IDENTIFY: {
name: 'MPIdentifyConfig',
},
+ SET_ONCE: {
+ name: 'MPSetOnceConfig',
+ },
PROFILE_ANDROID: {
name: 'MPProfilePropertiesAndroid',
},
diff --git a/src/v0/destinations/mp/data/MPSetOnceConfig.json b/src/v0/destinations/mp/data/MPSetOnceConfig.json
new file mode 100644
index 00000000000..e5aaf851a33
--- /dev/null
+++ b/src/v0/destinations/mp/data/MPSetOnceConfig.json
@@ -0,0 +1,122 @@
+[
+ {
+ "destKey": "$created",
+ "sourceKeys": "createdAt",
+ "required": false
+ },
+ {
+ "destKey": "$email",
+ "sourceKeys": "email",
+ "required": false
+ },
+ {
+ "destKey": "$first_name",
+ "sourceKeys": ["firstName", "firstname", "first_name"],
+ "required": false
+ },
+ {
+ "destKey": "$last_name",
+ "sourceKeys": ["lastName", "lastname", "last_name"],
+ "required": false
+ },
+ {
+ "destKey": "$name",
+ "sourceKeys": "name",
+ "required": false
+ },
+ {
+ "destKey": "$username",
+ "sourceKeys": ["username", "userName"],
+ "required": false
+ },
+ {
+ "destKey": "$phone",
+ "sourceKeys": "phone",
+ "required": false
+ },
+ {
+ "destKey": "$avatar",
+ "sourceKeys": "avatar",
+ "required": false
+ },
+ {
+ "destKey": "$country_code",
+ "sourceKeys": ["country", "address.country"],
+ "required": false
+ },
+ {
+ "destKey": "$city",
+ "sourceKeys": ["city", "address.city"],
+ "required": false
+ },
+ {
+ "destKey": "$region",
+ "sourceKeys": ["state", "address.state", "location.region"],
+ "required": false
+ },
+ {
+ "destKey": "$unsubscribed",
+ "sourceKeys": "unsubscribed",
+ "required": false
+ },
+ {
+ "destKey": "$geo_source",
+ "sourceKeys": "location.geoSource",
+ "required": false
+ },
+ {
+ "destKey": "$timezone",
+ "sourceKeys": "location.timezone",
+ "required": false
+ },
+ {
+ "destKey": "$latitude",
+ "sourceKeys": "location.latitude",
+ "required": false
+ },
+ {
+ "destKey": "$longitude",
+ "sourceKeys": "location.longitude",
+ "required": false
+ },
+ {
+ "destKey": "$carrier",
+ "sourceKeys": "network.carrier",
+ "required": false
+ },
+ {
+ "destKey": "$manufacturer",
+ "sourceKeys": "device.manufacturer",
+ "required": false
+ },
+ {
+ "destKey": "$model",
+ "sourceKeys": "device.model",
+ "required": false
+ },
+ {
+ "destKey": "$screen_height",
+ "sourceKeys": "screen.height",
+ "required": false
+ },
+ {
+ "destKey": "$screen_width",
+ "sourceKeys": "screen.width",
+ "required": false
+ },
+ {
+ "destKey": "$wifi",
+ "sourceKeys": "network.wifi",
+ "required": false
+ },
+ {
+ "destKey": "$initial_referrer",
+ "sourceKeys": "page.initial_referrer",
+ "required": false
+ },
+ {
+ "destKey": "$initial_referring_domain",
+ "sourceKeys": ["page.initial_referring_domain", "page.initialReferringDomain"],
+ "required": false
+ }
+]
diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js
index bb8d3e57566..3d0aaa7c4c6 100644
--- a/src/v0/destinations/mp/transform.js
+++ b/src/v0/destinations/mp/transform.js
@@ -17,6 +17,7 @@ const {
checkInvalidRtTfEvents,
handleRtTfSingleEventError,
groupEventsByType,
+ parseConfigArray,
} = require('../../util');
const {
ConfigCategory,
@@ -35,6 +36,7 @@ const {
combineBatchRequestsWithSameJobIds,
groupEventsByEndpoint,
batchEvents,
+ trimTraits,
} = require('./util');
const { CommonUtils } = require('../../../util/common');
@@ -226,17 +228,51 @@ const processTrack = (message, destination) => {
return returnValue;
};
+const createSetOnceResponse = (message, type, destination, setOnce) => {
+ const payload = {
+ $set_once: setOnce,
+ $token: destination.Config.token,
+ $distinct_id: message.userId || message.anonymousId,
+ };
+
+ if (destination?.Config.identityMergeApi === 'simplified') {
+ payload.$distinct_id = message.userId || `$device:${message.anonymousId}`;
+ }
+
+ return responseBuilderSimple(payload, message, type, destination.Config);
+};
+
const processIdentifyEvents = async (message, type, destination) => {
+ const messageClone = { ...message };
+ let seggregatedTraits = {};
const returnValue = [];
+ let setOnceProperties = [];
+
+ // making payload for set_once properties
+ if (destination.Config.setOnceProperties && destination.Config.setOnceProperties.length > 0) {
+ setOnceProperties = parseConfigArray(destination.Config.setOnceProperties, 'property');
+ seggregatedTraits = trimTraits(
+ messageClone.traits,
+ messageClone.context.traits,
+ setOnceProperties,
+ );
+ messageClone.traits = seggregatedTraits.traits;
+ messageClone.context.traits = seggregatedTraits.contextTraits;
+ if (Object.keys(seggregatedTraits.setOnce).length > 0) {
+ returnValue.push(
+ createSetOnceResponse(messageClone, type, destination, seggregatedTraits.setOnce),
+ );
+ }
+ }
// Creating the user profile
// https://developer.mixpanel.com/reference/profile-set
- returnValue.push(createIdentifyResponse(message, type, destination, responseBuilderSimple));
+ returnValue.push(createIdentifyResponse(messageClone, type, destination, responseBuilderSimple));
if (
destination.Config?.identityMergeApi !== 'simplified' &&
- message.userId &&
- message.anonymousId &&
+ messageClone.userId &&
+ messageClone.anonymousId &&
isImportAuthCredentialsAvailable(destination)
) {
// If userId and anonymousId both are present and required credentials for /import
@@ -245,13 +281,13 @@ const processIdentifyEvents = async (message, type, destination) => {
const trackPayload = {
event: '$merge',
properties: {
- $distinct_ids: [message.userId, message.anonymousId],
+ $distinct_ids: [messageClone.userId, messageClone.anonymousId],
token: destination.Config.token,
},
};
const identifyTrackResponse = responseBuilderSimple(
trackPayload,
- message,
+ messageClone,
'merge',
destination.Config,
);
@@ -440,7 +476,6 @@ const processRouterDest = async (inputs, reqMetadata) => {
destination: event.destination,
};
}
-
let processedEvents = await process(event);
processedEvents = CommonUtils.toArray(processedEvents);
return processedEvents.map((res) => ({
diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js
index bb4c23f1b44..bb8f36fdbe3 100644
--- a/src/v0/destinations/mp/util.js
+++ b/src/v0/destinations/mp/util.js
@@ -1,3 +1,4 @@
+const lodash = require('lodash');
const set = require('set-value');
const get = require('get-value');
const { InstrumentationError } = require('@rudderstack/integrations-lib');
@@ -14,6 +15,7 @@ const {
defaultBatchRequestConfig,
IsGzipSupported,
isObject,
+ isDefinedAndNotNullAndNotEmpty,
} = require('../../util');
const {
ConfigCategory,
@@ -26,6 +28,7 @@ const { CommonUtils } = require('../../../util/common');
const mPIdentifyConfigJson = mappingConfig[ConfigCategory.IDENTIFY.name];
const mPProfileAndroidConfigJson = mappingConfig[ConfigCategory.PROFILE_ANDROID.name];
const mPProfileIosConfigJson = mappingConfig[ConfigCategory.PROFILE_IOS.name];
+const mPSetOnceConfigJson = mappingConfig[ConfigCategory.SET_ONCE.name];
/**
* this function has been used to create
@@ -322,6 +325,72 @@ const combineBatchRequestsWithSameJobIds = (inputBatches) => {
return combineBatches(combineBatches(inputBatches));
};
+/**
+ * Trims the traits and contextTraits objects based on the setOnceProperties array and returns an object containing the modified traits, contextTraits, and setOnce properties.
+ *
+ * @param {object} traits - An object representing the traits.
+ * @param {object} contextTraits - An object representing the context traits.
+ * @param {string[]} setOnceProperties - An array of property paths to be considered for the setOnce transformation.
+ * @returns {object} - An object containing the modified traits, contextTraits, and setOnce properties.
+ *
+ * @example
+ * const traits = { name: 'John', age: 30 };
+ * const contextTraits = { country: 'USA', language: 'English', address: { city: 'New York', state: 'NY' }}};
+ * const setOnceProperties = ['name', 'country', 'address.city'];
+ *
+ * const result = trimTraits(traits, contextTraits, setOnceProperties);
+ * // Output: { traits: { age: 30 }, contextTraits: { language: 'English' }, setOnce: { $name: 'John', $country_code: 'USA', city: 'New York'} }
+ */
+function trimTraits(traits, contextTraits, setOnceProperties) {
+ let sentOnceTransformedPayload;
+ // Create a copy of the original traits object
+ const traitsCopy = { ...traits };
+ const contextTraitsCopy = { ...contextTraits };
+
+ // Initialize setOnce object
+ const setOnceEligible = {};
+
+ // Step 1: find the k-v pairs of setOnceProperties in traits and contextTraits
+
+ setOnceProperties.forEach((propertyPath) => {
+ const propName = lodash.last(propertyPath.split('.'));
+
+ const traitsValue = get(traitsCopy, propertyPath);
+ const contextTraitsValue = get(contextTraitsCopy, propertyPath);
+
+ if (isDefinedAndNotNullAndNotEmpty(traitsValue)) {
+ setOnceEligible[propName] = traitsValue;
+ lodash.unset(traitsCopy, propertyPath);
+ }
+ if (isDefinedAndNotNullAndNotEmpty(contextTraitsValue)) {
+ if (!setOnceEligible.hasOwnProperty(propName)) {
+ setOnceEligible[propName] = contextTraitsValue;
+ }
+ lodash.unset(contextTraitsCopy, propertyPath);
+ }
+ });
+
+ if (setOnceEligible && Object.keys(setOnceEligible).length > 0) {
+ // Step 2: transform properties eligible as per rudderstack declared identify event mapping
+ // setOnce should have all traits from message.traits and message.context.traits by now
+ sentOnceTransformedPayload = constructPayload(setOnceEligible, mPSetOnceConfigJson);
+
+ // Step 3: combine the transformed and custom setOnce traits
+ sentOnceTransformedPayload = extractCustomFields(
+ setOnceEligible,
+ sentOnceTransformedPayload,
+ 'root',
+ MP_IDENTIFY_EXCLUSION_LIST,
+ );
+ }
+
+ return {
+ traits: traitsCopy,
+ contextTraits: contextTraitsCopy,
+ setOnce: sentOnceTransformedPayload || {},
+ };
+}
+
module.exports = {
createIdentifyResponse,
isImportAuthCredentialsAvailable,
@@ -330,4 +399,5 @@ module.exports = {
generateBatchedPayloadForArray,
batchEvents,
combineBatchRequestsWithSameJobIds,
+ trimTraits,
};
diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js
index 6d5b24766df..fbaa6f9b9ff 100644
--- a/src/v0/destinations/mp/util.test.js
+++ b/src/v0/destinations/mp/util.test.js
@@ -4,6 +4,7 @@ const {
batchEvents,
generateBatchedPayloadForArray,
buildUtmParams,
+ trimTraits,
} = require('./util');
const { FEATURE_GZIP_SUPPORT } = require('../../util/constant');
@@ -602,4 +603,97 @@ describe('Mixpanel utils test', () => {
});
});
});
+ describe('Unit test cases for trimTraits', () => {
+ // Given a valid traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing traits, contextTraits, and setOnce properties.
+ it('should return an object containing traits, contextTraits, and setOnce properties when given valid inputs', () => {
+ const traits = { name: 'John', age: 30 };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = ['name', 'email'];
+
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
+ console.log(result);
+
+ expect(result).toEqual({
+ traits: {
+ age: 30,
+ },
+ contextTraits: {},
+ setOnce: { $name: 'John', $email: 'john@example.com' },
+ });
+ });
+
+ // Given an empty traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing empty traits and contextTraits, and an empty setOnce property.
+ it('should return an object containing empty traits and contextTraits, and an empty setOnce property when given empty traits and contextTraits objects', () => {
+ const traits = {};
+ const contextTraits = {};
+ const setOnceProperties = ['name', 'email'];
+
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
+
+ expect(result).toEqual({
+ traits: {},
+ contextTraits: {},
+ setOnce: {},
+ });
+ });
+
+ // Given an empty setOnceProperties array, the function should return an object containing the original traits and contextTraits objects, and an empty setOnce property.
+ it('should return an object containing the original traits and contextTraits objects, and an empty setOnce property when given an empty setOnceProperties array', () => {
+ const traits = { name: 'John', age: 30 };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = [];
+
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
+
+ expect(result).toEqual({
+ traits: { name: 'John', age: 30 },
+ contextTraits: { email: 'john@example.com' },
+ setOnce: {},
+ });
+ });
+
+ // Given a setOnceProperties array containing properties that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property.
+ it('should not add properties to the setOnce property when given setOnceProperties array with non-existent properties', () => {
+ const traits = { name: 'John', age: 30 };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = ['name', 'email', 'address'];
+
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
+
+ expect(result).toEqual({
+ traits: { age: 30 },
+ contextTraits: {},
+ setOnce: { $name: 'John', $email: 'john@example.com' },
+ });
+ });
+
+ // Given a setOnceProperties array containing properties with nested paths that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property.
+ it('should not add properties to the setOnce property when given setOnceProperties array with non-existent nested properties', () => {
+ const traits = { name: 'John', age: 30, address: 'kolkata' };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = ['name', 'email', 'address.city'];
+
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
+
+ expect(result).toEqual({
+ traits: { age: 30, address: 'kolkata' },
+ contextTraits: {},
+ setOnce: { $name: 'John', $email: 'john@example.com' },
+ });
+ });
+
+ it('should add properties to the setOnce property when given setOnceProperties array with existent nested properties', () => {
+ const traits = { name: 'John', age: 30, address: { city: 'kolkata' }, isAdult: false };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = ['name', 'email', 'address.city'];
+
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
+
+ expect(result).toEqual({
+ traits: { age: 30, address: {}, isAdult: false },
+ contextTraits: {},
+ setOnce: { $name: 'John', $email: 'john@example.com', $city: 'kolkata' },
+ });
+ });
+ });
});
diff --git a/src/v0/sources/shopify/util.js b/src/v0/sources/shopify/util.js
index 1da75cba3d4..61501bdab65 100644
--- a/src/v0/sources/shopify/util.js
+++ b/src/v0/sources/shopify/util.js
@@ -32,7 +32,10 @@ const getDataFromRedis = async (key, metricMetadata) => {
...metricMetadata,
});
const redisData = await RedisDB.getVal(key);
- if (redisData === null || (typeof redisData === "object" && Object.keys(redisData).length === 0)) {
+ if (
+ redisData === null ||
+ (typeof redisData === 'object' && Object.keys(redisData).length === 0)
+ ) {
stats.increment('shopify_redis_no_val', {
...metricMetadata,
});
diff --git a/src/v0/util/index.js b/src/v0/util/index.js
index 02968956622..fee1d7a96d4 100644
--- a/src/v0/util/index.js
+++ b/src/v0/util/index.js
@@ -2083,6 +2083,31 @@ const IsGzipSupported = (reqMetadata = {}) => {
return false;
};
+/**
+ * Returns an array containing the values of the specified key from each object in the input array.
+ * If the input array is falsy (null, undefined, empty array), an empty array is returned.
+ *
+ * @param {Array} arr - The input array from which values will be extracted.
+ * @param {string} key - The key of the property whose values will be extracted from each object in the input array.
+ * @returns {Array} - A new array containing the values of the specified key from each object in the input array.
+ *
+ * @example
+ * const configArray = [
+ * { name: 'John', age: 25 },
+ * { name: 'Jane', age: 30 },
+ * { name: 'Bob', age: 35 }
+ * ];
+ *
+ * const result = parseConfigArray(configArray, 'name');
+ * Output: ['John', 'Jane', 'Bob']
+ */
+const parseConfigArray = (arr, key) => {
+ if (!arr) {
+ return [];
+ }
+ return arr.map((item) => item[key]);
+};
+
// ========================================================================
// EXPORTS
// ========================================================================
@@ -2192,4 +2217,5 @@ module.exports = {
isValidInteger,
isNewStatusCodesAccepted,
IsGzipSupported,
+ parseConfigArray,
};
diff --git a/test/__tests__/data/sanity/active_campaign_output.json b/test/__tests__/data/sanity/active_campaign_output.json
deleted file mode 100644
index 544f9b937f2..00000000000
--- a/test/__tests__/data/sanity/active_campaign_output.json
+++ /dev/null
@@ -1,54 +0,0 @@
-[
- {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://active.campaigns.rudder.com/api/3/contact/sync",
- "headers": {
- "Content-Type": "application/json",
- "Api-Token": "dummyApiToken"
- },
- "params": {},
- "body": {
- "JSON": {
- "contact": {
- "email": "manashi@gmail.com",
- "phone": 9090909000,
- "firstName": null,
- "lastName": null
- },
- "apiKey": "dummyApiKey"
- },
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {}
- },
- "files": {}
- },
- {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://active.campaigns.rudder.com/api/3/contact/sync",
- "headers": {
- "Content-Type": "application/json",
- "Api-Token": "dummyApiToken"
- },
- "params": {},
- "body": {
- "JSON": {
- "contact": {
- "email": "testkolkata@rudderlabs.com",
- "phone": "570-690-4150",
- "firstName": "Sajal",
- "lastName": "Mohanta"
- },
- "apiKey": "dummyApiKey"
- },
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {}
- },
- "files": {}
- }
-]
diff --git a/test/__tests__/data/sanity/destination_config.json b/test/__tests__/data/sanity/destination_config.json
deleted file mode 100644
index 11c999aa9ce..00000000000
--- a/test/__tests__/data/sanity/destination_config.json
+++ /dev/null
@@ -1,102 +0,0 @@
-{
- "active_campaign": {
- "config": {
- "processor": {
- "Config": {
- "apiKey": "dummyApiKey",
- "apiUrl": "https://active.campaigns.rudder.com",
- "actid": "476550467",
- "eventKey": "f8a866fddc721350fdc2fbbd2e5c43a6dddaaa03"
- }
- }
- }
- },
- "clevertap": {
- "config": {
- "processor": {},
- "router": {}
- }
- },
- "marketo": {
- "config": {
- "router": {
- "Config": {
- "accountId": "marketo_acct_id_success",
- "clientId": "marketo_client_id_success",
- "clientSecret": "marketo_client_secret_success",
- "trackAnonymousEvents": true,
- "customActivityEventMap": [{ "from": "Product Clicked", "to": "100001" }],
- "customActivityPropertyMap": [{ "from": "name", "to": "productName" }],
- "customActivityPrimaryKeyMap": [{ "from": "Product Clicked", "to": "name" }],
- "leadTraitMapping": [{ "from": "leadScore", "to": "customLeadScore" }]
- },
- "secretConfig": {},
- "ID": "1mMy5cqbtfuaKZv1IhVQKnBdVwe",
- "name": "Marketo",
- "enabled": true,
- "workspaceId": "1TSN08muJTZwH8iCDmnnRt1pmLd",
- "deleted": false,
- "createdAt": "2020-12-30T08:39:32.005Z",
- "updatedAt": "2021-02-03T16:22:31.374Z",
- "destinationDefinition": {
- "config": {
- "destConfig": {
- "defaultConfig": [
- "accountId",
- "clientId",
- "clientSecret",
- "trackAnonymousEvents",
- "customActivityEventMap",
- "customActivityPropertyMap",
- "customActivityPrimaryKeyMap",
- "leadTraitMapping"
- ]
- },
- "secretKeys": ["clientSecret"],
- "excludeKeys": [],
- "includeKeys": [],
- "routerTransform": true,
- "supportedSourceTypes": [
- "android",
- "ios",
- "web",
- "unity",
- "amp",
- "cloud",
- "reactnative"
- ]
- },
- "responseRules": {
- "responseType": "JSON",
- "rules": {
- "retryable": [
- { "success": "false", "errors.0.code": 600 },
- { "success": "false", "errors.0.code": 601 },
- { "success": "false", "errors.0.code": 602 },
- { "success": "false", "errors.0.code": 604 },
- { "success": "false", "errors.0.code": 606 },
- { "success": "false", "errors.0.code": 607 },
- { "success": "false", "errors.0.code": 608 },
- { "success": "false", "errors.0.code": 611 }
- ],
- "abortable": [
- { "success": "false", "errors.0.code": 603 },
- { "success": "false", "errors.0.code": 605 },
- { "success": "false", "errors.0.code": 609 },
- { "success": "false", "errors.0.code": 610 }
- ]
- }
- },
- "id": "1aIXqM806xAVm92nx07YwKbRrO9",
- "name": "MARKETO",
- "displayName": "Marketo",
- "createdAt": "2020-04-09T09:24:31.794Z",
- "updatedAt": "2021-01-11T11:03:28.103Z"
- },
- "transformations": [],
- "isConnectionEnabled": true,
- "isProcessorEnabled": true
- }
- }
- }
-}
diff --git a/test/__tests__/data/sanity/marketo_router_output.json b/test/__tests__/data/sanity/marketo_router_output.json
deleted file mode 100644
index 9f967074a38..00000000000
--- a/test/__tests__/data/sanity/marketo_router_output.json
+++ /dev/null
@@ -1,165 +0,0 @@
-[
- {
- "batchedRequest": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://marketo_acct_id_success.mktorest.com/rest/v1/activities/external.json",
- "headers": {
- "Authorization": "Bearer access_token_success",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "JSON": {
- "input": [
- {
- "activityDate": "2020-12-17T21:00:59.176Z",
- "activityTypeId": 100001,
- "attributes": [],
- "leadId": 4,
- "primaryAttributeValue": "Test Product"
- }
- ]
- },
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {}
- },
- "files": {}
- },
- "metadata": [
- {
- "jobId": 1
- }
- ],
- "batched": false,
- "statusCode": 200,
- "destination": {
- "Config": {
- "accountId": "marketo_acct_id_success",
- "clientId": "marketo_client_id_success",
- "clientSecret": "marketo_client_secret_success",
- "trackAnonymousEvents": true,
- "customActivityEventMap": [
- {
- "from": "Product Clicked",
- "to": "100001"
- }
- ],
- "customActivityPropertyMap": [
- {
- "from": "name",
- "to": "productName"
- }
- ],
- "customActivityPrimaryKeyMap": [
- {
- "from": "Product Clicked",
- "to": "name"
- }
- ],
- "leadTraitMapping": [
- {
- "from": "leadScore",
- "to": "customLeadScore"
- }
- ]
- },
- "secretConfig": {},
- "ID": "1mMy5cqbtfuaKZv1IhVQKnBdVwe",
- "name": "Marketo",
- "enabled": true,
- "workspaceId": "1TSN08muJTZwH8iCDmnnRt1pmLd",
- "deleted": false,
- "createdAt": "2020-12-30T08:39:32.005Z",
- "updatedAt": "2021-02-03T16:22:31.374Z",
- "destinationDefinition": {
- "config": {
- "destConfig": {
- "defaultConfig": [
- "accountId",
- "clientId",
- "clientSecret",
- "trackAnonymousEvents",
- "customActivityEventMap",
- "customActivityPropertyMap",
- "customActivityPrimaryKeyMap",
- "leadTraitMapping"
- ]
- },
- "secretKeys": ["clientSecret"],
- "excludeKeys": [],
- "includeKeys": [],
- "routerTransform": true,
- "supportedSourceTypes": ["android", "ios", "web", "unity", "amp", "cloud", "reactnative"]
- },
- "responseRules": {
- "responseType": "JSON",
- "rules": {
- "retryable": [
- {
- "success": "false",
- "errors.0.code": 600
- },
- {
- "success": "false",
- "errors.0.code": 601
- },
- {
- "success": "false",
- "errors.0.code": 602
- },
- {
- "success": "false",
- "errors.0.code": 604
- },
- {
- "success": "false",
- "errors.0.code": 606
- },
- {
- "success": "false",
- "errors.0.code": 607
- },
- {
- "success": "false",
- "errors.0.code": 608
- },
- {
- "success": "false",
- "errors.0.code": 611
- }
- ],
- "abortable": [
- {
- "success": "false",
- "errors.0.code": 603
- },
- {
- "success": "false",
- "errors.0.code": 605
- },
- {
- "success": "false",
- "errors.0.code": 609
- },
- {
- "success": "false",
- "errors.0.code": 610
- }
- ]
- }
- },
- "id": "1aIXqM806xAVm92nx07YwKbRrO9",
- "name": "MARKETO",
- "displayName": "Marketo",
- "createdAt": "2020-04-09T09:24:31.794Z",
- "updatedAt": "2021-01-11T11:03:28.103Z"
- },
- "transformations": [],
- "isConnectionEnabled": true,
- "isProcessorEnabled": true
- }
- }
-]
diff --git a/test/__tests__/data/sanity/sanity_input.json b/test/__tests__/data/sanity/sanity_input.json
deleted file mode 100644
index 9dd99611603..00000000000
--- a/test/__tests__/data/sanity/sanity_input.json
+++ /dev/null
@@ -1,185 +0,0 @@
-{
- "messages": [
- {
- "type": "identify",
- "event": "identify",
- "sentAt": "2021-02-08T12:45:42.760Z",
- "userId": "User_111",
- "channel": "mobile",
- "context": {
- "os": {
- "name": "iOS",
- "version": "13.0"
- },
- "app": {
- "name": "MyApp",
- "build": "1",
- "version": "1.0",
- "namespace": "com.rudderlabs.MyApp"
- },
- "device": {
- "id": "e2b94e2d-8327-429c-9d91-792a80d189c8",
- "name": "iPhone 11 Pro Max",
- "type": "iOS",
- "model": "iPhone",
- "manufacturer": "Apple"
- },
- "locale": "en-US",
- "screen": {
- "width": 896,
- "height": 414,
- "density": 3
- },
- "traits": {
- "age": 24,
- "city": "Bangalore",
- "name": "Manashi Mazumder",
- "email": "manashi@gmail.com",
- "phone": 9090909000,
- "userId": "User_111",
- "anonymousId": "e2b94e2d-8327-429c-9d91-792a80d189c8"
- },
- "library": {
- "name": "rudder-ios-library",
- "version": "1.0.11"
- },
- "network": {
- "wifi": true,
- "carrier": "unavailable",
- "cellular": false,
- "bluetooth": false
- },
- "timezone": "Asia/Kolkata",
- "userAgent": "unknown"
- },
- "rudderId": "139d65bb-8a40-48c7-854c-06bbf44f686e",
- "messageId": "1612788330-7e1e60a8-fb7e-437d-81c1-5b000318d0cb",
- "anonymousId": "e2b94e2d-8327-429c-9d91-792a80d189c8",
- "integrations": {
- "All": true
- },
- "originalTimestamp": "2021-02-08T12:45:30.717Z"
- },
- {
- "type": "identify",
- "sentAt": "2021-04-15T19:43:53.393Z",
- "userId": "sajal1234",
- "channel": "web",
- "context": {
- "id": "ID101",
- "ip": "0.0.0.0",
- "os": {
- "name": "",
- "version": ""
- },
- "app": {
- "name": "RudderLabs JavaScript SDK",
- "build": "1.0.0",
- "version": "1.1.17",
- "namespace": "com.rudderlabs.javascript"
- },
- "page": {
- "url": "www.rudderstack.com",
- "path": "/path5",
- "title": "JS Test",
- "search": "My Page",
- "tab_url": "https://odd-rat-19.loca.lt/Rectified.html",
- "referrer": "www.google.com",
- "initial_referrer": "$direct",
- "referring_domain": "",
- "initial_referring_domain": ""
- },
- "event": "Sample Identify call",
- "locale": "en-US",
- "screen": {
- "density": 2.75
- },
- "traits": {
- "id": "ID101",
- "city": "east greenwich",
- "plan": "Open source",
- "tags": ["x0002x", "y0004y", "t0001t"],
- "email": "testkolkata@rudderlabs.com",
- "event": "Sample Identify call",
- "lists": [
- {
- "id": 2,
- "status": "unsubscribed"
- },
- {
- "id": 3,
- "status": "unsubscribe"
- }
- ],
- "phone": "570-690-4150",
- "state": "RI",
- "title": "Mr",
- "logins": 5,
- "mobile": "123456786",
- "rating": "Hot",
- "street": "19123 forest lane",
- "company": {
- "id": 378763009439,
- "name": "Rudderlabs",
- "industry": "IT",
- "employee_count": 1200
- },
- "country": "USA",
- "sent_at": "20210109134567",
- "birthDay": "2021/04/15",
- "category": "SampleIdentify",
- "industry": "ITES",
- "lastName": "Mohanta",
- "timezone": "Berlin",
- "Homephone": 9836543283,
- "createdAt": "2021/04/15",
- "fieldInfo": {
- "state": "California",
- "Address": "kolkata",
- "listBox": ["Option1", "Option2"],
- "CheckBox": ["Option1", "Option2", "Option3"],
- "TextArea": "This is a sample text area field value . it is tested for Active Campaign. It should have more than 100 words. Lorem ipsum is a dummy sentence to fill up any area. And also , it is used to fill up dummy website. So, it is wriiten by simply wasting time. ",
- "DateField": "1988-12-05",
- "HiddenField": "Hidden",
- "RadioButton": "Option1",
- "multiChoice": "Option 1",
- "DateTimeField": "2020-05-19T02:45:00-05:00"
- },
- "firstName": "Sajal",
- "isEnabled": true,
- "timestamp": "1403743443",
- "event_text": "identify",
- "leadSource": "WEB",
- "postalCode": "94115",
- "received_at": "202101091347654",
- "organizationId": 378763009439,
- "original_timestamp": "1403743443"
- },
- "library": {
- "name": "RudderLabs JavaScript SDK",
- "version": "1.1.17"
- },
- "sent_at": "20210109134567",
- "campaign": {},
- "timestamp": "1403743443",
- "userAgent": "Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86_arm Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36",
- "event_text": "identify",
- "externalId": [
- {
- "id": "0035g000002XN5MAAW",
- "type": "Salesforce-Contact"
- }
- ],
- "received_at": "202101091347654",
- "original_timestamp": "1403743443"
- },
- "rudderId": "bc2402b7-6796-4fa5-9beb-bad3761fc961",
- "messageId": "9d0df235-d354-4a81-8056-07dcb58081d7",
- "anonymousId": "anonIDfromAndroid",
- "integrations": {
- "All": true
- },
- "originalTimestamp": "2021-04-15T19:43:53.392Z"
- }
- ]
-}
diff --git a/test/__tests__/data/sanity/sanity_router_input.json b/test/__tests__/data/sanity/sanity_router_input.json
deleted file mode 100644
index 16b8ccffadc..00000000000
--- a/test/__tests__/data/sanity/sanity_router_input.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- "messages": [
- {
- "anonymousId": "anon_id_success",
- "channel": "mobile",
- "context": {
- "app": {
- "build": "1",
- "name": "TestAppName",
- "namespace": "com.android.sample",
- "version": "1.0"
- },
- "device": {
- "id": "anon_id_success",
- "manufacturer": "Google",
- "model": "Android SDK built for x86",
- "name": "generic_x86",
- "type": "android"
- },
- "library": {
- "name": "com.rudderstack.android.sdk.core",
- "version": "1.0.1-beta.1"
- },
- "locale": "en-US",
- "network": {
- "carrier": "Android",
- "bluetooth": false,
- "cellular": true,
- "wifi": true
- },
- "os": {
- "name": "Android",
- "version": "8.1.0"
- },
- "screen": {
- "density": 420,
- "height": 1794,
- "width": 1080
- },
- "timezone": "Asia/Kolkata",
- "traits": {
- "anonymousId": "anon_id_success"
- },
- "userAgent": "Dalvik/2.1.0 (Linux; U; Android 8.1.0; Android SDK built for x86 Build/OSM1.180201.007)"
- },
- "event": "Product Clicked",
- "integrations": {
- "All": true
- },
- "messageId": "id1",
- "properties": {
- "name": "Test Product"
- },
- "originalTimestamp": "2020-12-17T21:00:59.176Z",
- "type": "track",
- "sentAt": "2020-03-12T09:05:03.421Z"
- }
- ]
-}
diff --git a/test/__tests__/facebook_conversions.test.js b/test/__tests__/facebook_conversions.test.js
index a450952efe9..9495a85913d 100644
--- a/test/__tests__/facebook_conversions.test.js
+++ b/test/__tests__/facebook_conversions.test.js
@@ -23,6 +23,8 @@ const outputRouterDataFile = fs.readFileSync(
const inputRouterData = JSON.parse(inputRouterDataFile);
const expectedRouterData = JSON.parse(outputRouterDataFile);
+Date.now = jest.fn(() => new Date("2023-11-12T15:46:51.000Z")); // 2023-11-12T15:46:51.693229+05:30
+
describe(`${name} Tests`, () => {
describe("Processor", () => {
testData.forEach((dataPoint, index) => {
diff --git a/test/__tests__/legacyRouter.test.ts b/test/__tests__/legacyRouter.test.ts
index 768db08ca82..926f6e76d4e 100644
--- a/test/__tests__/legacyRouter.test.ts
+++ b/test/__tests__/legacyRouter.test.ts
@@ -1,6 +1,6 @@
import fs from 'fs';
import path from 'path';
-import DestinationController from '../../src/controllers/destination';
+import { DestinationController } from '../../src/controllers/destination';
const destArg = process.argv.filter((x) => x.startsWith('--destName='))[0]; // send arguments on which destination
const typeArg = process.argv.filter((x) => x.startsWith('--type='))[0]; // send argument on which function
diff --git a/test/__tests__/sanity.test.js b/test/__tests__/sanity.test.js
deleted file mode 100644
index ae5faa917cb..00000000000
--- a/test/__tests__/sanity.test.js
+++ /dev/null
@@ -1,204 +0,0 @@
-jest.mock("axios");
-const name = "Sanity";
-const fs = require("fs");
-const path = require("path");
-const version = "v0";
-const { getDirectories } = require("./util");
-const { mockaxios } = require("../__mocks__/network");
-
-// ********************************
-// Getting Started
-// ********************************
-// sanity-folder-structure
-// __tests__
-// |
-// |--sanity.test.js
-// |--data
-// | |
-// | |--sanity
-// | |
-// | {integration(s)}_output.json
-// | {integration(s)}_router_output.json
-// | sanity_input.json
-// | sanity_router_input.json
-// | destination_config.json
-
-// ------ destination_config.json ------
-// JSON to store the destination-config of all the destinations
-// Each destination-config object has a config object storing the
-// processor/ router (transform-at) configs
-// -------------------------------------
-// If transformation is only done at processor
-// only use the processor as key and store the
-// destination definition as value.
-//
-// Format :
-// {
-// "clevertap": {
-// "config": {
-// "processor": { ...destination_definition },
-// "router": { ...destination_definition }
-// }
-// }
-// ..
-// ..
-// }
-
-// ----- sanity_input.json -----
-// JSON to store the sanity input messages which will be
-// used to test sanity for all the destination.
-//
-// Format:
-// {
-// "messages": [
-// { sanity_input_message_1},
-// { sanity_input_message_2},
-// { sanity_input_message_3}
-// ...
-// ]
-// }
-
-// ----- sanity_router_input.json -----
-// JSON to store the sanity input messages which will be
-// used to test sanity for all the destination.
-// These inputs will be used for testing router
-// transformation for all destination which support it.
-//
-// Format:
-// {
-// "messages": [
-// { router_sanity_input_message_1},
-// { router_sanity_input_message_2},
-// { router_sanity_input_message_3}
-// ...
-// ]
-// }
-
-// ------ {integration(s)}_output.json ------
-// These are specific output for each of the destinations
-// given the sanity input. We are using jest result matcher
-// to check if the output is matching with the expected output.
-// Example clevertap_output.json
-// Format:
-// [
-// {sanity_output_for_message_1},
-// {sanity_output_for_message_2},
-// ..
-// ]
-
-// ------ {integration(s)}_router_output.json ------
-// These are specific output for each of the destinations
-// given the router sanity input.
-// ** CHECK IF THE PARTICULAR DESTINATION SUPPORTS ROUTER TRANSFORMATION **
-// Example clevertap_router_output.json
-// Format:
-// [
-// {router_sanity_output_for_message_1},
-// {router_sanity_output_for_message_2},
-// ..
-// ]
-
-// Parsing all the destination names from /v0/destinations dir structure
-// parsing it into an array of string. This keeping the destinations to test
-// dynamic.
-// const integrations = getDirectories(
-// path.resolve(__dirname, `../${version}/destinations/`)
-// );
-// For Testing Current:
-// Uncomment this Line and comment the above 3 lines
-const integrations = ["marketo"];
-
-// Parsing the sanity input JSON which will be used for testing each destination
-const processorSanityInput = JSON.parse(
- fs.readFileSync(path.resolve(__dirname, `./data/sanity/sanity_input.json`))
-);
-
-// Parsing the sanity router input JSON which will be used for testing each destination
-// which support it
-const routerSanityInputRouter = JSON.parse(
- fs.readFileSync(
- path.resolve(__dirname, `./data/sanity/sanity_router_input.json`)
- )
-);
-// Parsing the destination config JSON from which we will get the destination definitions
-// along with if the destination supports router-transformation
-const destinationConfig = JSON.parse(
- fs.readFileSync(
- path.resolve(__dirname, `./data/sanity/destination_config.json`)
- )
-);
-
-// Iterating each of the destinations
-integrations.forEach(intg => {
- // Getting the transformation object
- const transformer = require(`../../src/${version}/destinations/${intg}/transform`);
- // Getting the config for this particular destination
- const { config } = destinationConfig[`${intg}`];
- // Where the ransformation is done at (processor, router ..)
- Object.keys(config).forEach(processAt => {
- // Depending on the case
- switch (processAt) {
- case "processor":
- {
- // Parsing the expected data for this particular destination
- const expectedData = JSON.parse(
- fs.readFileSync(
- path.resolve(__dirname, `./data/sanity/${intg}_output.json`)
- )
- );
- // For each of the messages we are processing using the transformer
- processorSanityInput.messages.forEach((message, index) => {
- // Building the event object with specified destination-definition
- const event = {
- message,
- destination: config[`${processAt}`]
- };
- // Sending the event to transformer and matching the result with expected output
- it(`${name} - integration[Processor]: ${intg} payload:${index}`, async () => {
- try {
- const output = await transformer.process(event);
- expect(output).toEqual(expectedData[index]);
- } catch (error) {
- expect(error.message).toEqual(expectedData[index].error);
- }
- });
- });
- }
- break;
-
- case "router":
- {
- // Parsing the expected router output data for this particular destination
- const expectedData = JSON.parse(
- fs.readFileSync(
- path.resolve(
- __dirname,
- `./data/sanity/${intg}_router_output.json`
- )
- )
- );
- // For each of the messages we are processing using the router transformer
- routerSanityInputRouter.messages.forEach((message, index) => {
- // Building the event object with specified destination-definition
- const events = [
- {
- message,
- metadata: {
- jobId: 1
- },
- destination: config[`${processAt}`]
- }
- ];
- // Sending the event to router transformer of this destinationand matching the result with expected output
- it(`${name} - integration(Router): ${intg} payload:${index}`, async () => {
- const routerOutput = await transformer.processRouterDest(events);
- expect(routerOutput[0]).toEqual(expectedData[index]);
- });
- });
- }
- break;
- default:
- throw new Error("Undefined Transform-At Config");
- }
- });
-});
diff --git a/test/__tests__/user_transformation_ts.test.ts b/test/__tests__/user_transformation_ts.test.ts
index 418c42fe334..971476e5135 100644
--- a/test/__tests__/user_transformation_ts.test.ts
+++ b/test/__tests__/user_transformation_ts.test.ts
@@ -1,5 +1,5 @@
import fetch from 'node-fetch';
-import UserTransformService from '../../src/services/userTransform';
+import { UserTransformService } from '../../src/services/userTransform';
import { FeatureFlags, FEATURE_FILTER_CODE } from '../../src/middlewares/featureFlag';
jest.mock('node-fetch', () => jest.fn());
diff --git a/test/apitests/data_scenarios/source/v1/pipedream.json b/test/apitests/data_scenarios/source/v1/pipedream.json
index 4219f3f6b1d..1496471066e 100644
--- a/test/apitests/data_scenarios/source/v1/pipedream.json
+++ b/test/apitests/data_scenarios/source/v1/pipedream.json
@@ -46,4 +46,4 @@
}
}
]
-}
\ No newline at end of file
+}
diff --git a/test/controllerUtility/ctrl-utility.test.ts b/test/controllerUtility/ctrl-utility.test.ts
index c3a668d7e7b..bf9eef1846f 100644
--- a/test/controllerUtility/ctrl-utility.test.ts
+++ b/test/controllerUtility/ctrl-utility.test.ts
@@ -1,5 +1,5 @@
import { ProcessorTransformationRequest, RouterTransformationRequestData } from '../../src/types';
-import ControllerUtility from '../../src/controllers/util';
+import { ControllerUtility } from '../../src/controllers/util';
type timestampTestCases = {
caseName: string;
diff --git a/test/integrations/destinations/gladly/network.ts b/test/integrations/destinations/gladly/network.ts
new file mode 100644
index 00000000000..8c1c2287387
--- /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 00000000000..211fa781342
--- /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 00000000000..d3339d81083
--- /dev/null
+++ b/test/integrations/destinations/gladly/router/data.ts
@@ -0,0 +1,604 @@
+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,
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ 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,
+ },
+ ],
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/mp/common.ts b/test/integrations/destinations/mp/common.ts
index ad12566cc6e..76ed25a760d 100644
--- a/test/integrations/destinations/mp/common.ts
+++ b/test/integrations/destinations/mp/common.ts
@@ -20,4 +20,42 @@ const sampleDestination = {
Transformations: [],
};
-export { sampleDestination, defaultMockFns };
+const destinationWithSetOnceProperty = {
+ Config: {
+ apiSecret: 'dummySecret',
+ dataResidency: 'us',
+ identityMergeApi: 'simplified',
+ setOnceProperties: [
+ {
+ property: 'nationality',
+ },
+ {
+ property: 'firstName',
+ },
+ {
+ property: 'address.city',
+ },
+ ],
+ superProperties: [
+ {
+ property: 'random',
+ },
+ ],
+ token: 'dummyToken',
+ useNativeSDK: false,
+ useNewMapping: false,
+ userDeletionApi: 'engage',
+ whitelistedEvents: [],
+ },
+ DestinationDefinition: {
+ DisplayName: 'Kiss Metrics',
+ ID: '1WhbSZ6uA3H5ChVifHpfL2H6sie',
+ Name: 'MIXPANEL',
+ },
+ Enabled: true,
+ ID: '1WhcOCGgj9asZu850HvugU2C3Aq',
+ Name: 'Kiss Metrics',
+ Transformations: [],
+};
+
+export { sampleDestination, defaultMockFns, destinationWithSetOnceProperty };
diff --git a/test/integrations/destinations/mp/processor/data.ts b/test/integrations/destinations/mp/processor/data.ts
index 2745c09ecc6..a6ba51ee781 100644
--- a/test/integrations/destinations/mp/processor/data.ts
+++ b/test/integrations/destinations/mp/processor/data.ts
@@ -1,5 +1,5 @@
import { overrideDestination } from '../../../testUtils';
-import { sampleDestination, defaultMockFns } from '../common';
+import { sampleDestination, defaultMockFns, destinationWithSetOnceProperty } from '../common';
export const data = [
{
@@ -5852,4 +5852,273 @@ export const data = [
},
},
},
+ {
+ name: 'mp',
+ description: 'Test Set Once Property',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ description: 'Alias: with property beyond and within exclusion list',
+ destination: destinationWithSetOnceProperty,
+ message: {
+ anonymousId: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca',
+ channel: 'web',
+ context: {
+ app: {
+ build: '1.0.0',
+ name: 'RudderLabs JavaScript SDK',
+ namespace: 'com.rudderlabs.javascript',
+ version: '1.0.5',
+ },
+ ip: '0.0.0.0',
+ library: {
+ name: 'RudderLabs JavaScript SDK',
+ version: '1.0.5',
+ },
+ locale: 'en-GB',
+ os: {
+ name: '',
+ version: '',
+ },
+ screen: {
+ density: 2,
+ },
+ traits: {
+ address: {
+ city: 'Disney',
+ },
+ country: 'USA',
+ email: 'TestSanity@disney.com',
+ firstName: 'Mickey test',
+ lastName: 'VarChange',
+ createdAt: '2020-01-23T08:54:02.362Z',
+ nationality: 'USA',
+ random: 'superProp',
+ },
+ page: {
+ path: '/destinations/mixpanel',
+ referrer: '',
+ search: '',
+ title: '',
+ url: 'https://docs.rudderstack.com/destinations/mixpanel',
+ category: 'destination',
+ initial_referrer: 'https://docs.rudderstack.com',
+ initial_referring_domain: 'docs.rudderstack.com',
+ },
+ userAgent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36',
+ },
+ integrations: {
+ All: true,
+ },
+ page: {
+ path: '/destinations/mixpanel',
+ referrer: '',
+ search: '',
+ title: '',
+ url: 'https://docs.rudderstack.com/destinations/mixpanel',
+ category: 'destination',
+ initial_referrer: 'https://docs.rudderstack.com',
+ initial_referring_domain: 'docs.rudderstack.com',
+ },
+ request_ip: '[::1]:53709',
+ type: 'identify',
+ userId: 'Santiy',
+ },
+ },
+ ],
+ method: 'POST',
+ },
+ pathSuffix: '',
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.mixpanel.com/engage/',
+ headers: {},
+ params: {},
+ body: {
+ JSON: {},
+ JSON_ARRAY: {
+ batch:
+ '[{"$set_once":{"$first_name":"Mickey test","$city":"Disney","nationality":"USA"},"$token":"dummyToken","$distinct_id":"Santiy"}]',
+ },
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'Santiy',
+ },
+ statusCode: 200,
+ },
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.mixpanel.com/engage/',
+ headers: {},
+ params: {},
+ body: {
+ JSON: {},
+ JSON_ARRAY: {
+ batch:
+ '[{"$set":{"$created":"2020-01-23T08:54:02.362Z","$email":"TestSanity@disney.com","$country_code":"USA","$initial_referrer":"https://docs.rudderstack.com","$initial_referring_domain":"docs.rudderstack.com","random":"superProp","$lastName":"VarChange","$browser":"Chrome","$browser_version":"79.0.3945.117"},"$token":"dummyToken","$distinct_id":"Santiy","$ip":"0.0.0.0","$time":null}]',
+ },
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'Santiy',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'mp',
+ description: 'Test Set Once Property with anonymousId',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ description: 'Alias: with property beyond and within exclusion list',
+ destination: destinationWithSetOnceProperty,
+ message: {
+ anonymousId: 'dummyAnnonymousId',
+ channel: 'web',
+ context: {
+ app: {
+ build: '1.0.0',
+ name: 'RudderLabs JavaScript SDK',
+ namespace: 'com.rudderlabs.javascript',
+ version: '1.0.5',
+ },
+ ip: '0.0.0.0',
+ library: {
+ name: 'RudderLabs JavaScript SDK',
+ version: '1.0.5',
+ },
+ locale: 'en-GB',
+ os: {
+ name: '',
+ version: '',
+ },
+ screen: {
+ density: 2,
+ },
+ traits: {
+ address: {
+ city: 'Disney',
+ },
+ country: 'USA',
+ email: 'TestSanity@disney.com',
+ firstName: 'Mickey test',
+ lastName: 'VarChange',
+ createdAt: '2020-01-23T08:54:02.362Z',
+ nationality: 'USA',
+ random: 'superProp',
+ },
+ page: {
+ path: '/destinations/mixpanel',
+ referrer: '',
+ search: '',
+ title: '',
+ url: 'https://docs.rudderstack.com/destinations/mixpanel',
+ category: 'destination',
+ initial_referrer: 'https://docs.rudderstack.com',
+ initial_referring_domain: 'docs.rudderstack.com',
+ },
+ userAgent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36',
+ },
+ integrations: {
+ All: true,
+ },
+ page: {
+ path: '/destinations/mixpanel',
+ referrer: '',
+ search: '',
+ title: '',
+ url: 'https://docs.rudderstack.com/destinations/mixpanel',
+ category: 'destination',
+ initial_referrer: 'https://docs.rudderstack.com',
+ initial_referring_domain: 'docs.rudderstack.com',
+ },
+ request_ip: '[::1]:53709',
+ type: 'identify',
+ },
+ },
+ ],
+ method: 'POST',
+ },
+ pathSuffix: '',
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.mixpanel.com/engage/',
+ headers: {},
+ params: {},
+ body: {
+ JSON: {},
+ JSON_ARRAY: {
+ batch:
+ '[{"$set_once":{"$first_name":"Mickey test","$city":"Disney","nationality":"USA"},"$token":"dummyToken","$distinct_id":"$device:dummyAnnonymousId"}]',
+ },
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'dummyAnnonymousId',
+ },
+ statusCode: 200,
+ },
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.mixpanel.com/engage/',
+ headers: {},
+ params: {},
+ body: {
+ JSON: {},
+ JSON_ARRAY: {
+ batch:
+ '[{"$set":{"$created":"2020-01-23T08:54:02.362Z","$email":"TestSanity@disney.com","$country_code":"USA","$initial_referrer":"https://docs.rudderstack.com","$initial_referring_domain":"docs.rudderstack.com","random":"superProp","$lastName":"VarChange","$browser":"Chrome","$browser_version":"79.0.3945.117"},"$token":"dummyToken","$distinct_id":"$device:dummyAnnonymousId","$ip":"0.0.0.0","$time":null}]',
+ },
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'dummyAnnonymousId',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
];