From 8a8db1e8bf230731c675fe16798280e0053ded9e Mon Sep 17 00:00:00 2001 From: Dilip Kola Date: Mon, 12 Feb 2024 16:27:22 +0530 Subject: [PATCH 01/33] chore: upgrade workflow engine to 0.7.2 --- .gitignore | 2 +- package-lock.json | 16 ++++++++-------- package.json | 2 +- src/cdk/v2/handler.ts | 15 +++++++++++---- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index f96c3ac807..956605f139 100644 --- a/.gitignore +++ b/.gitignore @@ -132,7 +132,7 @@ dist # Others **/.DS_Store - +.dccache .idea diff --git a/package-lock.json b/package-lock.json index dd42fd3921..d32e378d2d 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.2.2", - "@rudderstack/workflow-engine": "^0.6.9", + "@rudderstack/workflow-engine": "^0.7.2", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -4535,17 +4535,17 @@ } }, "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==" + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.5.tgz", + "integrity": "sha512-+iH40g+ZA2ANgwjOITdEdZJLZV+ljR28Akn/dRoDia591tMu7PptyvDaAvl+m1DijWXddpLQ8SX9xaEcIdmqlw==" }, "node_modules/@rudderstack/workflow-engine": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.6.10.tgz", - "integrity": "sha512-3GRdnbB0BuSPWiKf4JsSpG7QuGffAFWkT5T0JLR7Jxps25gt+PgtjQiAlwrRhO5A0WeTJMIKTI7ctz6dGmJosg==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.7.2.tgz", + "integrity": "sha512-aXQvoXMekvXxxDG6Yc5P5l3PJIwqVA+EmJ2w4SnQ94BUHhbsybPjgGvyzD17MUTAdWEOtqS38SuzLflBs/5T4g==", "dependencies": { "@aws-crypto/sha256-js": "^5.0.0", - "@rudderstack/json-template-engine": "^0.8.1", + "@rudderstack/json-template-engine": "^0.8.4", "jsonata": "^2.0.3", "lodash": "^4.17.21", "object-sizeof": "^2.6.3", diff --git a/package.json b/package.json index ac6746ed20..0d8e528342 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", "@rudderstack/integrations-lib": "^0.2.2", - "@rudderstack/workflow-engine": "^0.6.9", + "@rudderstack/workflow-engine": "^0.7.2", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", diff --git a/src/cdk/v2/handler.ts b/src/cdk/v2/handler.ts index 47d6d10179..edd14e7298 100644 --- a/src/cdk/v2/handler.ts +++ b/src/cdk/v2/handler.ts @@ -50,16 +50,20 @@ export async function getWorkflowEngine( const workflowEnginePromiseMap = new Map(); -export function getCachedWorkflowEngine( +export async function getCachedWorkflowEngine( destName: string, feature: string, bindings: Record = {}, -): WorkflowEngine { +): Promise { // Create a new instance of the engine for the destination if needed // TODO: Use cache to avoid long living engine objects workflowEnginePromiseMap[destName] = workflowEnginePromiseMap[destName] || new Map(); if (!workflowEnginePromiseMap[destName][feature]) { - workflowEnginePromiseMap[destName][feature] = getWorkflowEngine(destName, feature, bindings); + workflowEnginePromiseMap[destName][feature] = await getWorkflowEngine( + destName, + feature, + bindings, + ); } return workflowEnginePromiseMap[destName][feature]; } @@ -97,5 +101,8 @@ export function executeStep( ): Promise { return workflowEngine .getStepExecutor(stepName) - .execute(input, Object.assign(workflowEngine.bindings, getEmptyExecutionBindings(), bindings)); + .execute( + input, + Object.assign(workflowEngine.getBindings(), getEmptyExecutionBindings(), bindings), + ); } From 69c83489c85486c9b2aed4a1292bd9f0aae9ca44 Mon Sep 17 00:00:00 2001 From: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Date: Mon, 12 Feb 2024 19:07:26 +0530 Subject: [PATCH 02/33] fix: amplitude batch output metadata (#3077) * fix: amplitude batching * refactor: remove commented code * refactor: remove redundant import --- src/v0/destinations/am/transform.js | 13 +- .../destination/batch/failure_batch.json | 403 +++++++++++++----- .../destinations/am/batch/data.ts | 132 +++--- 3 files changed, 369 insertions(+), 179 deletions(-) diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index 911ec51be0..d78a5f727f 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -22,13 +22,13 @@ const { getFieldValueFromMessage, getValueFromMessage, deleteObjectProperty, - getErrorRespEvents, removeUndefinedAndNullValues, isDefinedAndNotNull, isAppleFamily, isDefinedAndNotNullAndNotEmpty, simpleProcessRouterDest, isValidInteger, + handleRtTfSingleEventError, } = require('../../util'); const { BASE_URL, @@ -40,7 +40,6 @@ const { AMBatchSizeLimit, AMBatchEventLimit, } = require('./config'); -const tags = require('../../util/tags'); const AMUtils = require('./utils'); @@ -904,16 +903,10 @@ const batch = (destEvents) => { // this case shold not happen and should be filtered already // by the first pass of single event transformation if (messageEvent && !userId && !deviceId) { - const errorResponse = getErrorRespEvents( - metadata, - 400, + const MissingUserIdDeviceIdError = new InstrumentationError( 'Both userId and deviceId cannot be undefined', - { - [tags.TAG_NAMES.ERROR_CATEGORY]: tags.ERROR_CATEGORIES.DATA_VALIDATION, - [tags.TAG_NAMES.ERROR_TYPE]: tags.ERROR_TYPES.INSTRUMENTATION, - }, ); - respList.push(errorResponse); + respList.push(handleRtTfSingleEventError(ev, MissingUserIdDeviceIdError, {})); return; } /* check if not a JSON body or (userId length < 5 && batchEventsWithUserIdLengthLowerThanFive is false) or diff --git a/test/apitests/data_scenarios/destination/batch/failure_batch.json b/test/apitests/data_scenarios/destination/batch/failure_batch.json index 8063bc74a1..6352ca1a11 100644 --- a/test/apitests/data_scenarios/destination/batch/failure_batch.json +++ b/test/apitests/data_scenarios/destination/batch/failure_batch.json @@ -1051,125 +1051,314 @@ }, "output": [ { - "metadata": { - "userId": "<<>>testUser<<>>testUser", - "jobId": 2, - "sourceId": "27O0bmEEx3GgfmEhZHUcPwJQVWC", - "destinationId": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", - "attemptNum": 0, - "receivedAt": "2022-12-24T17:29:00.699+05:30", - "createdAt": "2022-12-24T11:59:03.125Z", - "firstAttemptedAt": "", - "transformAt": "processor", - "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg", - "secret": null, - "jobsT": { - "UUID": "aaa8b7c4-2600-478b-b275-01740e1ef50c", - "JobID": 2, - "UserID": "<<>>testUser<<>>testUser", - "CreatedAt": "2022-12-24T11:59:03.125515Z", - "ExpireAt": "2022-12-24T11:59:03.125515Z", - "CustomVal": "AM", - "EventCount": 1, - "EventPayload": { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "events": [ - { - "ip": "[::1]", - "time": 1671883143047, - "library": "rudderstack", - "user_id": "testUser", - "device_id": "anon-id", - "insert_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9", - "event_type": "Product Added", - "session_id": -1, - "user_properties": { - "email": "test.c97@gmail.com", - "phone": "+919876543210", - "gender": "Male", - "lastName": "Rudderlabs", - "firstName": "test" - }, - "event_properties": { - "sku": "F15", - "url": "https://www.website.com/product/path", - "name": "Game", - "brand": "Gamepro", - "price": 13.49, - "coupon": "DISC21", - "variant": "111", - "category": "Games", - "position": 1, - "quantity": 11, - "image_url": "https://www.website.com/product/path.png", - "product_id": "123" + "metadata": [ + { + "userId": "<<>>testUser<<>>testUser", + "jobId": 2, + "sourceId": "27O0bmEEx3GgfmEhZHUcPwJQVWC", + "destinationId": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", + "attemptNum": 0, + "receivedAt": "2022-12-24T17:29:00.699+05:30", + "createdAt": "2022-12-24T11:59:03.125Z", + "firstAttemptedAt": "", + "transformAt": "processor", + "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg", + "secret": null, + "jobsT": { + "UUID": "aaa8b7c4-2600-478b-b275-01740e1ef50c", + "JobID": 2, + "UserID": "<<>>testUser<<>>testUser", + "CreatedAt": "2022-12-24T11:59:03.125515Z", + "ExpireAt": "2022-12-24T11:59:03.125515Z", + "CustomVal": "AM", + "EventCount": 1, + "EventPayload": { + "body": { + "XML": {}, + "FORM": {}, + "JSON": { + "events": [ + { + "ip": "[::1]", + "time": 1671883143047, + "library": "rudderstack", + "user_id": "testUser", + "device_id": "anon-id", + "insert_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9", + "event_type": "Product Added", + "session_id": -1, + "user_properties": { + "email": "test.c97@gmail.com", + "phone": "+919876543210", + "gender": "Male", + "lastName": "Rudderlabs", + "firstName": "test" + }, + "event_properties": { + "sku": "F15", + "url": "https://www.website.com/product/path", + "name": "Game", + "brand": "Gamepro", + "price": 13.49, + "coupon": "DISC21", + "variant": "111", + "category": "Games", + "position": 1, + "quantity": 11, + "image_url": "https://www.website.com/product/path.png", + "product_id": "123" + } } + ], + "api_key": "dummyApiKey", + "options": { + "min_id_length": 1 } - ], - "api_key": "dummyApiKey", - "options": { - "min_id_length": 1 - } + }, + "JSON_ARRAY": {} }, - "JSON_ARRAY": {} + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "userId": "anon-id", + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://api2.amplitude.com/2/httpapi" }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "anon-id", - "headers": { - "Content-Type": "application/json" + "PayloadSize": 1133, + "LastJobStatus": { + "JobID": 0, + "JobState": "", + "AttemptNum": 0, + "ExecTime": "0001-01-01T00:00:00Z", + "RetryTime": "0001-01-01T00:00:00Z", + "ErrorCode": "", + "ErrorResponse": null, + "Parameters": null, + "WorkspaceId": "" }, - "version": "1", - "endpoint": "https://api2.amplitude.com/2/httpapi" - }, - "PayloadSize": 1133, - "LastJobStatus": { - "JobID": 0, - "JobState": "", - "AttemptNum": 0, - "ExecTime": "0001-01-01T00:00:00Z", - "RetryTime": "0001-01-01T00:00:00Z", - "ErrorCode": "", - "ErrorResponse": null, - "Parameters": null, - "WorkspaceId": "" - }, - "Parameters": { - "record_id": null, - "source_id": "27O0bmEEx3GgfmEhZHUcPwJQVWC", - "event_name": "Product Added", - "event_type": "track", - "message_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9", - "received_at": "2022-12-24T17:29:00.699+05:30", - "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg", - "transform_at": "processor", - "source_job_id": "", - "destination_id": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", - "gateway_job_id": 1, - "source_task_id": "", - "source_batch_id": "", - "source_category": "", - "source_job_run_id": "", - "source_task_run_id": "", - "source_definition_id": "1b6gJdqOPOCadT3cddw8eidV591", - "destination_definition_id": "" + "Parameters": { + "record_id": null, + "source_id": "27O0bmEEx3GgfmEhZHUcPwJQVWC", + "event_name": "Product Added", + "event_type": "track", + "message_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9", + "received_at": "2022-12-24T17:29:00.699+05:30", + "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg", + "transform_at": "processor", + "source_job_id": "", + "destination_id": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", + "gateway_job_id": 1, + "source_task_id": "", + "source_batch_id": "", + "source_category": "", + "source_job_run_id": "", + "source_task_run_id": "", + "source_definition_id": "1b6gJdqOPOCadT3cddw8eidV591", + "destination_definition_id": "" + }, + "WorkspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg" }, - "WorkspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg" - }, - "workerAssignedTime": "2022-12-24T17:29:04.051596+05:30" - }, + "workerAssignedTime": "2022-12-24T17:29:04.051596+05:30" + } + ], "batched": false, "statusCode": 400, "statTags": { "errorCategory": "dataValidation", "errorType": "instrumentation" }, - "error": "Both userId and deviceId cannot be undefined" + "error": "Both userId and deviceId cannot be undefined", + "destination": { + "ID": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", + "Name": "Amplitude-2", + "DestinationDefinition": { + "ID": "1QGzO4fWSyq3lsyFHf4eQAMDSr9", + "Name": "AM", + "DisplayName": "Amplitude", + "Config": { + "destConfig": { + "android": [ + "eventUploadPeriodMillis", + "eventUploadThreshold", + "useNativeSDK", + "enableLocationListening", + "trackSessionEvents", + "useAdvertisingIdForDeviceId" + ], + "defaultConfig": [ + "apiKey", + "groupTypeTrait", + "groupValueTrait", + "trackAllPages", + "trackCategorizedPages", + "trackNamedPages", + "traitsToIncrement", + "traitsToSetOnce", + "traitsToAppend", + "traitsToPrepend", + "trackProductsOnce", + "trackRevenuePerProduct", + "versionName", + "apiSecret", + "residencyServer", + "blacklistedEvents", + "whitelistedEvents", + "eventFilteringOption", + "mapDeviceBrand" + ], + "flutter": [ + "eventUploadPeriodMillis", + "eventUploadThreshold", + "useNativeSDK", + "enableLocationListening", + "trackSessionEvents", + "useAdvertisingIdForDeviceId", + "useIdfaAsDeviceId" + ], + "ios": [ + "eventUploadPeriodMillis", + "eventUploadThreshold", + "useNativeSDK", + "trackSessionEvents", + "useIdfaAsDeviceId" + ], + "reactnative": [ + "eventUploadPeriodMillis", + "eventUploadThreshold", + "useNativeSDK", + "enableLocationListening", + "trackSessionEvents", + "useAdvertisingIdForDeviceId", + "useIdfaAsDeviceId" + ], + "web": [ + "useNativeSDK", + "preferAnonymousIdForDeviceId", + "deviceIdFromUrlParam", + "forceHttps", + "trackGclid", + "trackReferrer", + "saveParamsReferrerOncePerSession", + "trackUtmProperties", + "unsetParamsReferrerOnNewSession", + "batchEvents", + "eventUploadPeriodMillis", + "eventUploadThreshold", + "oneTrustCookieCategories" + ] + }, + "excludeKeys": [], + "includeKeys": [ + "apiKey", + "groupTypeTrait", + "groupValueTrait", + "trackAllPages", + "trackCategorizedPages", + "trackNamedPages", + "traitsToIncrement", + "traitsToSetOnce", + "traitsToAppend", + "traitsToPrepend", + "trackProductsOnce", + "trackRevenuePerProduct", + "preferAnonymousIdForDeviceId", + "deviceIdFromUrlParam", + "forceHttps", + "trackGclid", + "trackReferrer", + "saveParamsReferrerOncePerSession", + "trackUtmProperties", + "unsetParamsReferrerOnNewSession", + "batchEvents", + "eventUploadPeriodMillis", + "eventUploadThreshold", + "versionName", + "enableLocationListening", + "useAdvertisingIdForDeviceId", + "trackSessionEvents", + "useIdfaAsDeviceId", + "blacklistedEvents", + "whitelistedEvents", + "oneTrustCookieCategories", + "eventFilteringOption", + "mapDeviceBrand" + ], + "saveDestinationResponse": true, + "secretKeys": ["apiKey", "apiSecret"], + "supportedMessageTypes": ["alias", "group", "identify", "page", "screen", "track"], + "supportedSourceTypes": [ + "android", + "ios", + "web", + "unity", + "amp", + "cloud", + "warehouse", + "reactnative", + "flutter", + "cordova" + ], + "supportsVisualMapper": true, + "transformAt": "processor", + "transformAtV1": "processor" + }, + "ResponseRules": null + }, + "Config": { + "apiKey": "dummyApiKey", + "apiSecret": "", + "blacklistedEvents": [ + { + "eventName": "" + } + ], + "eventFilteringOption": "disable", + "groupTypeTrait": "", + "groupValueTrait": "", + "mapDeviceBrand": false, + "residencyServer": "standard", + "trackAllPages": false, + "trackCategorizedPages": true, + "trackNamedPages": true, + "trackProductsOnce": false, + "trackRevenuePerProduct": false, + "traitsToAppend": [ + { + "traits": "" + } + ], + "traitsToIncrement": [ + { + "traits": "" + } + ], + "traitsToPrepend": [ + { + "traits": "" + } + ], + "traitsToSetOnce": [ + { + "traits": "" + } + ], + "versionName": "", + "whitelistedEvents": [ + { + "eventName": "" + } + ] + }, + "Enabled": true, + "WorkspaceID": "27O0bhB6p5ehfOWeeZlOSsSDTLg", + "Transformations": [], + "IsProcessorEnabled": true, + "RevisionID": "2JMKUgZX3b8sbtDSZrkUB7okeOY" + } }, { "batchedRequest": { diff --git a/test/integrations/destinations/am/batch/data.ts b/test/integrations/destinations/am/batch/data.ts index aa67df06c7..91a17606a9 100644 --- a/test/integrations/destinations/am/batch/data.ts +++ b/test/integrations/destinations/am/batch/data.ts @@ -61,7 +61,7 @@ export const data = [ endpoint: 'https://api.eu.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -83,16 +83,24 @@ export const data = [ { batched: false, error: 'Both userId and deviceId cannot be undefined', - //TODO fix this - metadata: { - job_id: 1, - userId: 'u1', - }, + metadata: [ + { + jobId: 1, + userId: 'u1', + }, + ], statTags: { errorCategory: 'dataValidation', errorType: 'instrumentation', }, statusCode: 400, + destination: { + ID: 'a', + url: 'a', + Config: { + residencyServer: 'EU', + }, + }, }, ], }, @@ -163,7 +171,7 @@ export const data = [ endpoint: 'https://api.eu.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -218,7 +226,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 2, + jobId: 2, userId: 'u1', }, destination: { @@ -273,7 +281,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 3, + jobId: 3, userId: 'u1', }, destination: { @@ -331,7 +339,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 4, + jobId: 4, userId: 'u1', }, destination: { @@ -389,7 +397,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 5, + jobId: 5, userId: 'u1', }, destination: { @@ -423,7 +431,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/groupidentify', }, metadata: { - job_id: 6, + jobId: 6, userId: 'u1', }, destination: { @@ -455,7 +463,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/usermap', }, metadata: { - job_id: 7, + jobId: 7, userId: 'u1', }, destination: { @@ -529,7 +537,7 @@ export const data = [ }, metadata: [ { - job_id: 1, + jobId: 1, userId: 'u1', }, ], @@ -565,7 +573,7 @@ export const data = [ }, metadata: [ { - job_id: 6, + jobId: 6, userId: 'u1', }, ], @@ -599,7 +607,7 @@ export const data = [ }, metadata: [ { - job_id: 7, + jobId: 7, userId: 'u1', }, ], @@ -710,19 +718,19 @@ export const data = [ }, metadata: [ { - job_id: 2, + jobId: 2, userId: 'u1', }, { - job_id: 3, + jobId: 3, userId: 'u1', }, { - job_id: 4, + jobId: 4, userId: 'u1', }, { - job_id: 5, + jobId: 5, userId: 'u1', }, ], @@ -801,7 +809,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -854,7 +862,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 2, + jobId: 2, userId: 'u1', }, destination: { @@ -907,7 +915,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 3, + jobId: 3, userId: 'u1', }, destination: { @@ -963,7 +971,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 4, + jobId: 4, userId: 'u1', }, destination: { @@ -1019,7 +1027,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 5, + jobId: 5, userId: 'u1', }, destination: { @@ -1053,7 +1061,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/groupidentify', }, metadata: { - job_id: 6, + jobId: 6, userId: 'u1', }, destination: { @@ -1085,7 +1093,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/usermap', }, metadata: { - job_id: 7, + jobId: 7, userId: 'u1', }, destination: { @@ -1157,7 +1165,7 @@ export const data = [ }, metadata: [ { - job_id: 1, + jobId: 1, userId: 'u1', }, ], @@ -1193,7 +1201,7 @@ export const data = [ }, metadata: [ { - job_id: 6, + jobId: 6, userId: 'u1', }, ], @@ -1227,7 +1235,7 @@ export const data = [ }, metadata: [ { - job_id: 7, + jobId: 7, userId: 'u1', }, ], @@ -1338,19 +1346,19 @@ export const data = [ }, metadata: [ { - job_id: 2, + jobId: 2, userId: 'u1', }, { - job_id: 3, + jobId: 3, userId: 'u1', }, { - job_id: 4, + jobId: 4, userId: 'u1', }, { - job_id: 5, + jobId: 5, userId: 'u1', }, ], @@ -2117,7 +2125,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -2172,7 +2180,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 2, + jobId: 2, userId: 'u1', }, destination: { @@ -2227,7 +2235,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 3, + jobId: 3, userId: 'u1', }, destination: { @@ -2285,7 +2293,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 4, + jobId: 4, userId: 'u1', }, destination: { @@ -2343,7 +2351,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 5, + jobId: 5, userId: 'u1', }, destination: { @@ -2377,7 +2385,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/groupidentify', }, metadata: { - job_id: 6, + jobId: 6, userId: 'u1', }, destination: { @@ -2409,7 +2417,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/usermap', }, metadata: { - job_id: 7, + jobId: 7, userId: 'u1', }, destination: { @@ -2483,7 +2491,7 @@ export const data = [ }, metadata: [ { - job_id: 1, + jobId: 1, userId: 'u1', }, ], @@ -2519,7 +2527,7 @@ export const data = [ }, metadata: [ { - job_id: 6, + jobId: 6, userId: 'u1', }, ], @@ -2553,7 +2561,7 @@ export const data = [ }, metadata: [ { - job_id: 7, + jobId: 7, userId: 'u1', }, ], @@ -2664,19 +2672,19 @@ export const data = [ }, metadata: [ { - job_id: 2, + jobId: 2, userId: 'u1', }, { - job_id: 3, + jobId: 3, userId: 'u1', }, { - job_id: 4, + jobId: 4, userId: 'u1', }, { - job_id: 5, + jobId: 5, userId: 'u1', }, ], @@ -2756,7 +2764,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -2811,7 +2819,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 2, + jobId: 2, userId: 'u1', }, destination: { @@ -2866,7 +2874,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 3, + jobId: 3, userId: 'u1', }, destination: { @@ -2924,7 +2932,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 4, + jobId: 4, userId: 'u1', }, destination: { @@ -2982,7 +2990,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 5, + jobId: 5, userId: 'u1', }, destination: { @@ -3016,7 +3024,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/groupidentify', }, metadata: { - job_id: 6, + jobId: 6, userId: 'u1', }, destination: { @@ -3048,7 +3056,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/usermap', }, metadata: { - job_id: 7, + jobId: 7, userId: 'u1', }, destination: { @@ -3121,7 +3129,7 @@ export const data = [ }, metadata: [ { - job_id: 1, + jobId: 1, userId: 'u1', }, ], @@ -3157,7 +3165,7 @@ export const data = [ }, metadata: [ { - job_id: 6, + jobId: 6, userId: 'u1', }, ], @@ -3191,7 +3199,7 @@ export const data = [ }, metadata: [ { - job_id: 7, + jobId: 7, userId: 'u1', }, ], @@ -3302,19 +3310,19 @@ export const data = [ }, metadata: [ { - job_id: 2, + jobId: 2, userId: 'u1', }, { - job_id: 3, + jobId: 3, userId: 'u1', }, { - job_id: 4, + jobId: 4, userId: 'u1', }, { - job_id: 5, + jobId: 5, userId: 'u1', }, ], From a98cabdfe7781ada12baf742df4a3a439fc5fecd Mon Sep 17 00:00:00 2001 From: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:01:41 +0530 Subject: [PATCH 03/33] fix: gaoc batching order (#3064) * fix: gaoc batching order * refactor: apply review suggestions --- .../transform.js | 3 +- src/v0/destinations/mp/transform.js | 2 +- src/v0/destinations/mp/util.js | 91 ------ src/v0/destinations/mp/util.test.js | 230 -------------- src/v0/util/index.js | 85 +++++ src/v0/util/index.test.js | 292 ++++++++++++++++- .../router/data.ts | 294 ++++++++---------- 7 files changed, 502 insertions(+), 495 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 397895c603..46cde72771 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -10,6 +10,7 @@ const { defaultBatchRequestConfig, getSuccessRespEvents, checkInvalidRtTfEvents, + combineBatchRequestsWithSameJobIds, } = require('../../util'); const { CALL_CONVERSION, @@ -229,7 +230,7 @@ const processRouterDest = async (inputs, reqMetadata) => { .concat(storeSalesEventsBatchedResponseList) .concat(clickCallEvents) .concat(errorRespList); - return batchedResponseList; + return combineBatchRequestsWithSameJobIds(batchedResponseList); }; module.exports = { process, processRouterDest }; diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js index 41a814683d..24890c0eb1 100644 --- a/src/v0/destinations/mp/transform.js +++ b/src/v0/destinations/mp/transform.js @@ -18,6 +18,7 @@ const { handleRtTfSingleEventError, groupEventsByType, parseConfigArray, + combineBatchRequestsWithSameJobIds, } = require('../../util'); const { ConfigCategory, @@ -33,7 +34,6 @@ const { createIdentifyResponse, isImportAuthCredentialsAvailable, buildUtmParams, - combineBatchRequestsWithSameJobIds, groupEventsByEndpoint, batchEvents, trimTraits, diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js index c01d2308b7..8e943f41dd 100644 --- a/src/v0/destinations/mp/util.js +++ b/src/v0/destinations/mp/util.js @@ -139,44 +139,6 @@ const isImportAuthCredentialsAvailable = (destination) => destination.Config.serviceAccountUserName && destination.Config.projectId); -/** - * Finds an existing batch based on metadata JobIds from the provided batch and metadataMap. - * @param {*} batch - * @param {*} metadataMap The map containing metadata items indexed by JobIds. - * @returns - */ -const findExistingBatch = (batch, metadataMap) => { - let existingBatch = null; - - // eslint-disable-next-line no-restricted-syntax - for (const metadataItem of batch.metadata) { - if (metadataMap.has(metadataItem.jobId)) { - existingBatch = metadataMap.get(metadataItem.jobId); - break; - } - } - - return existingBatch; -}; - -/** - * Removes duplicate metadata within each merged batch object. - * @param {*} mergedBatches An array of merged batch objects. - */ -const removeDuplicateMetadata = (mergedBatches) => { - mergedBatches.forEach((batch) => { - const metadataSet = new Set(); - // eslint-disable-next-line no-param-reassign - batch.metadata = batch.metadata.filter((metadataItem) => { - if (!metadataSet.has(metadataItem.jobId)) { - metadataSet.add(metadataItem.jobId); - return true; - } - return false; - }); - }); -}; - /** * Builds UTM parameters from a campaign object. * @@ -273,58 +235,6 @@ const batchEvents = (successRespList, maxBatchSize, reqMetadata) => { }); }; -/** - * Combines batched requests with the same JobIds. - * @param {*} inputBatches The array of batched request objects. - * @returns The combined batched requests with merged JobIds. - * - */ -const combineBatchRequestsWithSameJobIds = (inputBatches) => { - const combineBatches = (batches) => { - const clonedBatches = [...batches]; - const mergedBatches = []; - const metadataMap = new Map(); - - clonedBatches.forEach((batch) => { - const existingBatch = findExistingBatch(batch, metadataMap); - - if (existingBatch) { - // Merge batchedRequests arrays - existingBatch.batchedRequest = [ - ...(Array.isArray(existingBatch.batchedRequest) - ? existingBatch.batchedRequest - : [existingBatch.batchedRequest]), - ...(Array.isArray(batch.batchedRequest) ? batch.batchedRequest : [batch.batchedRequest]), - ]; - - // Merge metadata - batch.metadata.forEach((metadataItem) => { - if (!metadataMap.has(metadataItem.jobId)) { - metadataMap.set(metadataItem.jobId, existingBatch); - } - existingBatch.metadata.push(metadataItem); - }); - } else { - mergedBatches.push(batch); - batch.metadata.forEach((metadataItem) => { - metadataMap.set(metadataItem.jobId, batch); - }); - } - }); - - // Remove duplicate metadata within each merged object - removeDuplicateMetadata(mergedBatches); - - return mergedBatches; - }; - // We need to run this twice because in first pass some batches might not get merged - // and in second pass they might get merged - // Example: [[{jobID:1}, {jobID:2}], [{jobID:3}], [{jobID:1}, {jobID:3}]] - // 1st pass: [[{jobID:1}, {jobID:2}, {jobID:3}], [{jobID:3}]] - // 2nd pass: [[{jobID:1}, {jobID:2}, {jobID:3}]] - 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. * @@ -398,6 +308,5 @@ module.exports = { groupEventsByEndpoint, generateBatchedPayloadForArray, batchEvents, - combineBatchRequestsWithSameJobIds, trimTraits, }; diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js index ebf140fadd..866119a336 100644 --- a/src/v0/destinations/mp/util.test.js +++ b/src/v0/destinations/mp/util.test.js @@ -1,5 +1,4 @@ const { - combineBatchRequestsWithSameJobIds, groupEventsByEndpoint, batchEvents, generateBatchedPayloadForArray, @@ -263,235 +262,6 @@ describe('Mixpanel utils test', () => { }); }); - describe('Unit test cases for combineBatchRequestsWithSameJobIds', () => { - it('Combine batch request with same jobIds', async () => { - const input = [ - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - metadata: [ - { - jobId: 1, - }, - { - jobId: 4, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/import/', - }, - metadata: [ - { - jobId: 3, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 1, - }, - { - jobId: 3, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/import/', - }, - metadata: [ - { - jobId: 6, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - ]; - - const expectedOutput = [ - { - batchedRequest: [ - { - endpoint: 'https://api.mixpanel.com/track/', - }, - { - endpoint: 'https://api.mixpanel.com/engage/', - }, - { - endpoint: 'https://api.mixpanel.com/import/', - }, - ], - metadata: [ - { - jobId: 1, - }, - { - jobId: 4, - }, - { - jobId: 3, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/import/', - }, - metadata: [ - { - jobId: 6, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - ]; - expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); - }); - - it('Each batchRequest contains unique jobIds (no event multiplexing)', async () => { - const input = [ - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - metadata: [ - { - jobId: 1, - }, - { - jobId: 4, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 2, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - ]; - - const expectedOutput = [ - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - - metadata: [ - { - jobId: 1, - }, - { - jobId: 4, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 2, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - ]; - expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); - }); - }); - describe('Unit test cases for generateBatchedPayloadForArray', () => { it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { const events = [ diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 49ef39969e..0cc66b2d7a 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -33,6 +33,7 @@ const { AUTH_STATUS_INACTIVE, } = require('../../adapters/networkhandler/authConstants'); const { FEATURE_FILTER_CODE, FEATURE_GZIP_SUPPORT } = require('./constant'); +const { CommonUtils } = require('../../util/common'); // ======================================================================== // INLINERS @@ -2136,6 +2137,87 @@ const parseConfigArray = (arr, key) => { return arr.map((item) => item[key]); }; +/** + * Finds an existing batch based on metadata JobIds from the provided batch and metadataMap. + * @param {*} batch + * @param {*} metadataMap The map containing metadata items indexed by JobIds. + * @returns + */ +const findExistingBatch = (batch, metadataMap) => { + const existingMetadataItem = batch.metadata.find((metadataItem) => + metadataMap.has(metadataItem.jobId), + ); + return existingMetadataItem ? metadataMap.get(existingMetadataItem.jobId) : null; +}; + +/** + * Removes duplicate metadata within each merged batch object. + * @param {*} mergedBatches An array of merged batch objects. + */ +const removeDuplicateMetadata = (mergedBatches) => { + mergedBatches.forEach((batch) => { + const metadataSet = new Set(); + // eslint-disable-next-line no-param-reassign + batch.metadata = batch.metadata.filter((metadataItem) => { + if (!metadataSet.has(metadataItem.jobId)) { + metadataSet.add(metadataItem.jobId); + return true; + } + return false; + }); + }); +}; + +/** + * Combines batched requests with the same JobIds. + * @param {*} inputBatches The array of batched request objects. + * @returns The combined batched requests with merged JobIds. + * + */ +const combineBatchRequestsWithSameJobIds = (inputBatches) => { + const combineBatches = (batches) => { + const clonedBatches = [...batches]; + const mergedBatches = []; + const metadataMap = new Map(); + + clonedBatches.forEach((batch) => { + const existingBatch = findExistingBatch(batch, metadataMap); + + if (existingBatch) { + // Merge batchedRequests arrays + existingBatch.batchedRequest = [ + ...CommonUtils.toArray(existingBatch.batchedRequest), + ...CommonUtils.toArray(batch.batchedRequest), + ]; + + // Merge metadata + batch.metadata.forEach((metadataItem) => { + if (!metadataMap.has(metadataItem.jobId)) { + metadataMap.set(metadataItem.jobId, existingBatch); + } + existingBatch.metadata.push(metadataItem); + }); + } else { + mergedBatches.push(batch); + batch.metadata.forEach((metadataItem) => { + metadataMap.set(metadataItem.jobId, batch); + }); + } + }); + + // Remove duplicate metadata within each merged object + removeDuplicateMetadata(mergedBatches); + + return mergedBatches; + }; + // We need to run this twice because in first pass some batches might not get merged + // and in second pass they might get merged + // Example: [[{jobID:1}, {jobID:2}], [{jobID:3}], [{jobID:1}, {jobID:3}]] + // 1st pass: [[{jobID:1}, {jobID:2}, {jobID:3}], [{jobID:3}]] + // 2nd pass: [[{jobID:1}, {jobID:2}, {jobID:3}]] + return combineBatches(combineBatches(inputBatches)); +}; + // ======================================================================== // EXPORTS // ======================================================================== @@ -2249,4 +2331,7 @@ module.exports = { isNewStatusCodesAccepted, IsGzipSupported, parseConfigArray, + findExistingBatch, + removeDuplicateMetadata, + combineBatchRequestsWithSameJobIds, }; diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js index 1c6b34eca6..4dc6255691 100644 --- a/src/v0/util/index.test.js +++ b/src/v0/util/index.test.js @@ -2,7 +2,12 @@ const { TAG_NAMES } = require('@rudderstack/integrations-lib'); const utilities = require('.'); const { getFuncTestData } = require('../../../test/testHelper'); const { FilteredEventsError } = require('./errorTypes'); -const { hasCircularReference, flattenJson, generateExclusionList } = require('./index'); +const { + hasCircularReference, + flattenJson, + generateExclusionList, + combineBatchRequestsWithSameJobIds, +} = require('./index'); // Names of the utility functions to test const functionNames = [ @@ -166,3 +171,288 @@ describe('generateExclusionList', () => { expect(result).toEqual(expected); }); }); + +describe('Unit test cases for combineBatchRequestsWithSameJobIds', () => { + it('Combine batch request with same jobIds', async () => { + const input = [ + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + metadata: [ + { + jobId: 1, + }, + { + jobId: 4, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint2', + }, + metadata: [ + { + jobId: 3, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + metadata: [ + { + jobId: 5, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 1, + }, + { + jobId: 3, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint2', + }, + metadata: [ + { + jobId: 6, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + ]; + + const expectedOutput = [ + { + batchedRequest: [ + { + endpoint: 'https://endpoint1', + }, + { + endpoint: 'https://endpoint3', + }, + { + endpoint: 'https://endpoint2', + }, + ], + metadata: [ + { + jobId: 1, + }, + { + jobId: 4, + }, + { + jobId: 3, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + metadata: [ + { + jobId: 5, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint2', + }, + metadata: [ + { + jobId: 6, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + ]; + expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); + }); + + it('Each batchRequest contains unique jobIds (no event multiplexing)', async () => { + const input = [ + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + metadata: [ + { + jobId: 1, + }, + { + jobId: 4, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 2, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 5, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + ]; + + const expectedOutput = [ + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + + metadata: [ + { + jobId: 1, + }, + { + jobId: 4, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 2, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 5, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + ]; + expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); + }); +}); diff --git a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts index 1536af8187..a38980f0e9 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts @@ -96,6 +96,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, + jobId: 1, userId: 'u1', }, destination: { @@ -211,6 +212,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, + jobId: 2, userId: 'u1', }, destination: { @@ -274,7 +276,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 1, + jobId: 3, userId: 'u1', }, destination: { @@ -350,7 +352,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 2, + jobId: 4, userId: 'u1', }, destination: { @@ -426,7 +428,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 3, + jobId: 5, userId: 'u1', }, destination: { @@ -479,80 +481,130 @@ export const data = [ body: { output: [ { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://googleads.googleapis.com/v14/customers/7693729833/offlineUserDataJobs', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - }, - params: { event: 'Store sales', customerId: '7693729833' }, - body: { - JSON: { - event: '7693729833', - isStoreConversion: true, - createJobPayload: { - job: { - storeSalesMetadata: { - loyaltyFraction: 1, - transaction_upload_fraction: '1', + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://googleads.googleapis.com/v14/customers/7693729833/offlineUserDataJobs', + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + }, + params: { event: 'Store sales', customerId: '7693729833' }, + body: { + JSON: { + event: '7693729833', + isStoreConversion: true, + createJobPayload: { + job: { + storeSalesMetadata: { + loyaltyFraction: 1, + transaction_upload_fraction: '1', + }, + type: 'STORE_SALES_UPLOAD_FIRST_PARTY', }, - type: 'STORE_SALES_UPLOAD_FIRST_PARTY', }, - }, - addConversionPayload: { - operations: [ - { - create: { - transaction_attribute: { - store_attribute: { store_code: 'store code' }, - transaction_amount_micros: '100000000', - order_id: 'order id', - currency_code: 'INR', - transaction_date_time: '2019-10-14 16:45:18+05:30', - }, - userIdentifiers: [ - { - hashedEmail: - '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + addConversionPayload: { + operations: [ + { + create: { + transaction_attribute: { + store_attribute: { store_code: 'store code' }, + transaction_amount_micros: '100000000', + order_id: 'order id', + currency_code: 'INR', + transaction_date_time: '2019-10-14 16:45:18+05:30', }, - ], - }, - }, - { - create: { - transaction_attribute: { - store_attribute: { store_code: 'store code2' }, - transaction_amount_micros: '100000000', - order_id: 'order id', - currency_code: 'INR', - transaction_date_time: '2019-10-14 16:45:18+05:30', + userIdentifiers: [ + { + hashedEmail: + '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + }, + ], }, - userIdentifiers: [ - { - hashedEmail: - '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + }, + { + create: { + transaction_attribute: { + store_attribute: { store_code: 'store code2' }, + transaction_amount_micros: '100000000', + order_id: 'order id', + currency_code: 'INR', + transaction_date_time: '2019-10-14 16:45:18+05:30', }, - ], + userIdentifiers: [ + { + hashedEmail: + '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + }, + ], + }, }, + ], + enable_partial_failure: false, + enable_warnings: false, + validate_only: true, + }, + executeJobPayload: { validate_only: true }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://googleads.googleapis.com/v14/customers/7693729833:uploadCallConversions', + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + }, + params: { + event: 'Order Completed', + customerId: '7693729833', + customVariables: [{ from: '', to: '' }], + properties: { + loyaltyFraction: 1, + order_id: 'order id', + currency: 'INR', + revenue: '100', + store_code: 'store code2', + email: 'alex@example.com', + gclid: 'gclid', + product_id: '123445', + quantity: 123, + callerId: '1234', + callStartDateTime: '2019-10-14T11:15:18.299Z', + }, + }, + body: { + JSON: { + conversions: [ + { + callerId: '1234', + callStartDateTime: '2019-10-14T11:15:18.299Z', + conversionDateTime: '2019-10-14 16:45:18+05:30', + conversionValue: 100, + currencyCode: 'INR', }, ], - enable_partial_failure: false, - enable_warnings: false, - validate_only: true, + partialFailure: true, }, - executeJobPayload: { validate_only: true }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, + files: {}, }, - files: {}, - }, + ], metadata: [ { secret: { @@ -560,7 +612,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 1, + jobId: 3, userId: 'u1', }, { @@ -569,7 +621,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 2, + jobId: 4, userId: 'u1', }, ], @@ -714,6 +766,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, + jobId: 1, userId: 'u1', }, ], @@ -824,6 +877,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, + jobId: 2, userId: 'u1', }, ], @@ -857,108 +911,6 @@ export const data = [ }, }, }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://googleads.googleapis.com/v14/customers/7693729833:uploadCallConversions', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - }, - params: { - event: 'Order Completed', - customerId: '7693729833', - customVariables: [{ from: '', to: '' }], - properties: { - loyaltyFraction: 1, - order_id: 'order id', - currency: 'INR', - revenue: '100', - store_code: 'store code2', - email: 'alex@example.com', - gclid: 'gclid', - product_id: '123445', - quantity: 123, - callerId: '1234', - callStartDateTime: '2019-10-14T11:15:18.299Z', - }, - }, - body: { - JSON: { - conversions: [ - { - callerId: '1234', - callStartDateTime: '2019-10-14T11:15:18.299Z', - conversionDateTime: '2019-10-14 16:45:18+05:30', - conversionValue: 100, - currencyCode: 'INR', - }, - ], - partialFailure: true, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - secret: { - access_token: 'abcd1234', - refresh_token: 'efgh5678', - developer_token: 'ijkl91011', - }, - jobId: 2, - userId: 'u1', - }, - ], - batched: false, - statusCode: 200, - destination: { - Config: { - rudderAccountId: '2Hsy2iFyoG5VLDd9wQcggHLMYFA', - customerId: '769-372-9833', - subAccount: false, - UserIdentifierSource: 'FIRST_PARTY', - conversionEnvironment: 'none', - defaultUserIdentifier: 'email', - hashUserIdentifier: true, - validateOnly: true, - eventsToOfflineConversionsTypeMapping: [ - { from: 'Data Reading Guide', to: 'click' }, - { from: 'Order Completed', to: 'store' }, - { from: 'Sign-up - click', to: 'click' }, - { from: 'Outbound click (rudderstack.com)', to: 'click' }, - { from: 'Page view', to: 'click' }, - { from: 'download', to: 'click' }, - { from: 'Product Clicked', to: 'store' }, - { from: 'Order Completed', to: 'call' }, - ], - loginCustomerId: '4219454086', - eventsToConversionsNamesMapping: [ - { from: 'Data Reading Guide', to: 'Data Reading Guide' }, - { from: 'Order Completed', to: 'Order Completed' }, - { from: 'Sign-up - click', to: 'Sign-up - click' }, - { - from: 'Outbound click (rudderstack.com)', - to: 'Outbound click (rudderstack.com)', - }, - { from: 'Page view', to: 'Page view' }, - { from: 'Sign up completed', to: 'Sign-up - click' }, - { from: 'download', to: 'Page view' }, - { from: 'Product Clicked', to: 'Store sales' }, - ], - authStatus: 'active', - oneTrustCookieCategories: [], - customVariables: [{ from: '', to: '' }], - }, - }, - }, { metadata: [ { @@ -967,7 +919,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 3, + jobId: 5, userId: 'u1', }, ], From d522b35c908a9f262ba3ba27dda0ea5d9ac5bc6b Mon Sep 17 00:00:00 2001 From: Sandeep Digumarty Date: Tue, 13 Feb 2024 16:41:12 +0530 Subject: [PATCH 04/33] fix: resolve bugsnag issue caused due to undefined properties (#3086) --- src/v0/util/facebookUtils/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v0/util/facebookUtils/index.js b/src/v0/util/facebookUtils/index.js index 4c09518559..7fa1e898fe 100644 --- a/src/v0/util/facebookUtils/index.js +++ b/src/v0/util/facebookUtils/index.js @@ -147,7 +147,7 @@ const getContentType = (message, defaultValue, categoryToContent, destinationNam return integrationsObj.contentType; } - let { category } = properties; + let { category } = properties || {}; if (!category) { const { products } = properties; if (products && products.length > 0 && Array.isArray(products) && isObject(products[0])) { From f7ec0a1244a7b97e6b40de5ed9881c63300866dc Mon Sep 17 00:00:00 2001 From: Anant Jain <62471433+anantjain45823@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:42:29 +0530 Subject: [PATCH 05/33] fix: amplitude: Error handling for missing event type (#3079) * fix: amplitude: Error handling for missing event type * chore: small fix for undefined userDefinedTemplate * Update src/v0/destinations/am/transform.js Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> * chore: comments addressed * fix: test cases --------- Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> --- src/v0/destinations/am/transform.js | 7 +-- src/v0/destinations/am/util.test.js | 33 +++++++++++- src/v0/destinations/am/utils.js | 15 ++++++ .../destinations/am/processor/data.ts | 53 +++++++++++++++++++ 4 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index d78a5f727f..afd72b77e1 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -614,16 +614,16 @@ const processSingleMessage = (message, destination) => { case EventType.PAGE: if (useUserDefinedPageEventName) { const getMessagePath = userProvidedPageEventString - .substring( + ?.substring( userProvidedPageEventString.indexOf('{') + 2, userProvidedPageEventString.indexOf('}'), ) .trim(); evType = - userProvidedPageEventString.trim() === '' + userProvidedPageEventString?.trim() === '' ? name : userProvidedPageEventString - .trim() + ?.trim() .replaceAll(/{{([^{}]+)}}/g, get(message, getMessagePath)); } else { evType = `Viewed ${name || get(message, CATEGORY_KEY) || ''} Page`; @@ -701,6 +701,7 @@ const processSingleMessage = (message, destination) => { logger.debug('could not determine type'); throw new InstrumentationError('message type not supported'); } + AMUtils.validateEventType(evType); return responseBuilderSimple( groupInfo, payloadObjectName, diff --git a/src/v0/destinations/am/util.test.js b/src/v0/destinations/am/util.test.js index faaa9170f0..455f9117ef 100644 --- a/src/v0/destinations/am/util.test.js +++ b/src/v0/destinations/am/util.test.js @@ -1,4 +1,4 @@ -const { getUnsetObj } = require('./utils'); +const { getUnsetObj, validateEventType } = require('./utils'); describe('getUnsetObj', () => { it("should return undefined when 'message.integrations.Amplitude.fieldsToUnset' is not array", () => { @@ -64,3 +64,34 @@ describe('getUnsetObj', () => { expect(result).toBeUndefined(); }); }); + + +describe('validateEventType', () => { + + it('should validate event type when it is valid with only page name given', () => { + expect(() => { + validateEventType('Home Page'); + }).not.toThrow(); + }); + + it('should throw an error when event type is null', () => { + expect(() => { + validateEventType(null); + }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }); + + it('should throw an error when event type is undefined', () => { + expect(() => { + validateEventType(undefined); + }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }); + + // Function receives an empty string as event type + it('should throw an error when event type is an empty string', () => { + expect(() => { + validateEventType(''); + }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }); + +}); + diff --git a/src/v0/destinations/am/utils.js b/src/v0/destinations/am/utils.js index 71fe0ab459..e51d09aaa7 100644 --- a/src/v0/destinations/am/utils.js +++ b/src/v0/destinations/am/utils.js @@ -10,6 +10,7 @@ // populate these dest keys const get = require('get-value'); const uaParser = require('@amplitude/ua-parser-js'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); const logger = require('../../../logger'); const { isDefinedAndNotNull } = require('../../util'); @@ -108,6 +109,19 @@ const getUnsetObj = (message) => { return unsetObject; }; + +/** + * Check for evType as in some cases, like when the page name is absent, + * either the template depends only on the event.name or there is no template provided by user + * @param {*} evType + */ +const validateEventType = (evType) => { + if (!isDefinedAndNotNull(evType) || (typeof evType === "string" && evType.length ===0)) { + throw new InstrumentationError( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); + } +}; module.exports = { getOSName, getOSVersion, @@ -117,4 +131,5 @@ module.exports = { getBrand, getEventId, getUnsetObj, + validateEventType }; diff --git a/test/integrations/destinations/am/processor/data.ts b/test/integrations/destinations/am/processor/data.ts index f28606da0c..b645fb5ac7 100644 --- a/test/integrations/destinations/am/processor/data.ts +++ b/test/integrations/destinations/am/processor/data.ts @@ -11327,4 +11327,57 @@ export const data = [ }, }, }, + { + name: 'am', + description: + 'Test 78 -> Page call invalid event type as page name and template is not provided', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + request_ip: '1.1.1.1', + type: 'page', + userId: '12345', + properties: {}, + integrations: { + All: true, + }, + sentAt: '2019-10-14T11:15:53.296Z', + }, + destination: { + Config: { + apiKey: 'abcde', + useUserDefinedPageEventName: true, + userProvidedPageEventString: '', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 400, + error: + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'AM', + module: 'destination', + implementation: 'native', + feature: 'processor', + }, + }, + ], + }, + }, + }, ]; From b6edff46fa0e0e210e82206fea46a064e3fbe00f Mon Sep 17 00:00:00 2001 From: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:57:10 +0530 Subject: [PATCH 06/33] fix: tiktok ads v2 error handling (#3084) * fix: tiktok ads v2 error handling * refactor: remove redundant import --- src/v0/destinations/tiktok_ads/transformV2.js | 2 +- .../destinations/tiktok_ads/processor/data.ts | 103 ++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/v0/destinations/tiktok_ads/transformV2.js b/src/v0/destinations/tiktok_ads/transformV2.js index 91078dfe65..98f7d61e1e 100644 --- a/src/v0/destinations/tiktok_ads/transformV2.js +++ b/src/v0/destinations/tiktok_ads/transformV2.js @@ -40,7 +40,7 @@ const getTrackResponsePayload = (message, destConfig, event) => { } // if contents is not present but we have properties.products present which has fields with superset of contents fields - if (payload.properties && !payload.properties.contents && message.properties.products) { + if (!payload.properties?.contents && message.properties?.products) { // retreiving data from products only when contents is not present payload.properties.contents = getContents(message, false); } diff --git a/test/integrations/destinations/tiktok_ads/processor/data.ts b/test/integrations/destinations/tiktok_ads/processor/data.ts index 9d7c3a8d10..3b68426fbf 100644 --- a/test/integrations/destinations/tiktok_ads/processor/data.ts +++ b/test/integrations/destinations/tiktok_ads/processor/data.ts @@ -6870,4 +6870,107 @@ export const data = [ }, }, }, + { + name: 'tiktok_ads', + description: 'Test 46 -> V2 -> Event with no properties', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + anonymousId: '21e13f4bc7ceddad', + channel: 'web', + context: { + traits: { + email: 'dd6ff77f54e2106661089bae4d40cdb600979bf7edc9eb65c0942ba55c7c2d7f', + }, + userAgent: + 'Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion', + ip: '13.57.97.131', + locale: 'en-US', + externalId: [ + { + type: 'tiktokExternalId', + id: 'f0e388f53921a51f0bb0fc8a2944109ec188b59172935d8f23020b1614cc44bc', + }, + ], + }, + messageId: '84e26acc-56a5-4835-8233-591137fca468', + session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22', + originalTimestamp: '2019-10-14T09:03:17.562Z', + timestamp: '2020-09-17T19:49:27Z', + type: 'track', + event: 'customEvent', + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + destination: { + Config: { + version: 'v2', + accessToken: 'dummyAccessToken', + pixelCode: '{{PIXEL-CODE}}', + hashUserProperties: false, + sendCustomEvents: true, + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/event/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_source: 'web', + event_source_id: '{{PIXEL-CODE}}', + partner_name: 'RudderStack', + data: [ + { + event: 'customEvent', + event_id: '84e26acc-56a5-4835-8233-591137fca468', + event_time: 1600372167, + properties: { content_type: 'product' }, + user: { + locale: 'en-US', + email: 'dd6ff77f54e2106661089bae4d40cdb600979bf7edc9eb65c0942ba55c7c2d7f', + external_id: + 'f0e388f53921a51f0bb0fc8a2944109ec188b59172935d8f23020b1614cc44bc', + ip: '13.57.97.131', + user_agent: + 'Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion', + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; From 6a364fba34c46b15c0fe4b06ecfa6f4b81b6f436 Mon Sep 17 00:00:00 2001 From: Mihir Bhalala <77438541+mihir-4116@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:39:25 +0530 Subject: [PATCH 07/33] fix(ga4): failures not considered with 200 status in events tab (#3089) --- src/v0/destinations/ga4/networkHandler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v0/destinations/ga4/networkHandler.js b/src/v0/destinations/ga4/networkHandler.js index b62fcc8d3b..2cb98e1460 100644 --- a/src/v0/destinations/ga4/networkHandler.js +++ b/src/v0/destinations/ga4/networkHandler.js @@ -30,9 +30,9 @@ const responseHandler = (destinationResponse, dest) => { const { description, validationCode, fieldPath } = response.validationMessages[0]; throw new NetworkError( `Validation Server Response Handler:: Validation Error for ${dest} of field path :${fieldPath} | ${validationCode}-${description}`, - status, + 400, { - [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(400), }, response?.validationMessages[0]?.description, ); From 08d1bf046cf2750b91fedc94493bc2b640085777 Mon Sep 17 00:00:00 2001 From: Dilip Kola <33080863+koladilip@users.noreply.github.com> Date: Thu, 15 Feb 2024 17:11:49 +0530 Subject: [PATCH 08/33] Update pull_request_template.md --- .github/pull_request_template.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fe308ce8b9..cfcb1fc0d8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -26,6 +26,8 @@ N/A N/A +@coderabbitai review +
### Developer checklist From b5cdccb75fe68150816140174087fddad677db10 Mon Sep 17 00:00:00 2001 From: Anant Jain <62471433+anantjain45823@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:13:28 +0530 Subject: [PATCH 09/33] feat: tiktok_offline_events added support for all Standard events (#3094) * feat: tiktok_offline_events added support for all Standard events * chore: comments addressed * Update src/v0/destinations/tiktok_ads_offline_events/config.js Co-authored-by: Dilip Kola <33080863+koladilip@users.noreply.github.com> --------- Co-authored-by: Dilip Kola <33080863+koladilip@users.noreply.github.com> --- .../tiktok_ads_offline_events/config.js | 20 +- .../processor/data.ts | 244 ++++++++++++++++++ 2 files changed, 260 insertions(+), 4 deletions(-) diff --git a/src/v0/destinations/tiktok_ads_offline_events/config.js b/src/v0/destinations/tiktok_ads_offline_events/config.js index 5d5e80c716..3c58b42a44 100644 --- a/src/v0/destinations/tiktok_ads_offline_events/config.js +++ b/src/v0/destinations/tiktok_ads_offline_events/config.js @@ -19,12 +19,24 @@ const CONFIG_CATEGORIES = { const PARTNER_NAME = 'RudderStack'; const EVENT_NAME_MAPPING = { + 'addpaymentinfo': 'AddPaymentInfo', + 'addtocart': 'AddToCart', + 'addtowishlist': 'AddToWishlist', + 'checkout started': 'InitiateCheckout', 'checkout step completed': 'CompletePayment', - contact: 'Contact', - submitform: 'SubmitForm', - subscribe: 'Subscribe', + 'clickbutton': 'ClickButton', + 'completeregistration': 'CompleteRegistration', + 'contact': 'Contact', + 'download': 'Download', + 'order completed': 'PlaceAnOrder', + 'payment info entered': 'AddPaymentInfo', + 'product added': 'AddToCart', + 'product added to wishlist': 'AddToWishlist', + 'search': 'Search', + 'submitform': 'SubmitForm', + 'subscribe': 'Subscribe', + 'viewcontent': 'ViewContent', }; - const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); module.exports = { diff --git a/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts b/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts index 2b9341b851..81e125eaca 100644 --- a/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts +++ b/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts @@ -614,4 +614,248 @@ export const data = [ }, }, }, + { + name: 'tiktok_ads_offline_events', + description: 'Test 7 -> `search` standard tiktok Event through event mapping from UI', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + accessToken: 'dummyAccessToken', + hashUserProperties: true, + eventsToStandard: [ + { + from: 'custom event', + to: 'Search', + }, + ], + }, + }, + message: { + type: 'track', + event: 'custom event', + sentAt: '2023-03-22T00:02:33.802Z', + traits: { + email: [ + 'efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + phone: [ + 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + }, + userId: '60241286212', + channel: 'sources', + context: { + sources: { + job_id: '2N4WuoNQpGYmCPASUvnV86QyhY4/Syncher', + version: 'v1.20.0', + job_run_id: 'cgd4a063b2fn2e1j0q90', + task_run_id: 'cgd4a063b2fn2e1j0qa0', + }, + }, + recordId: '16322', + rudderId: '5b4ed73f-69aa-4198-88d1-3d4d509acbf1', + messageId: 'cgd4b663b2fn2e1j8th0', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + phone: 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + value: 32.839999999999996, + emails: + '["efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900","078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f","","","","","","","",""]', + eventId: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + currency: 'USD', + order_id: 60241286212, + eventSetId: '7211223771099742210', + event_name: 'CompletePayment', + }, + receivedAt: '2023-03-22T00:02:33.171Z', + request_ip: '10.7.78.187', + anonymousId: '60241286212', + originalTimestamp: '2023-03-22T00:02:33.802Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/offline/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_set_id: '7211223771099742210', + event_id: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + order_id: 60241286212, + currency: 'USD', + value: 32.839999999999996, + }, + event: 'Search', + partner_name: 'RudderStack', + context: { + user: { + emails: [ + '4dc75b075057df6f6b729e74a9feed1244dcf8ceb7903eaba13203f3268ae4b9', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + phone_numbers: [ + '28b7b205c2936d2ded022d2587fb2677a76e560e921b3ad615b739b0238baa5d', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + event_set_id: '7211223771099742210', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'tiktok_ads_offline_events', + description: + 'Test 8 -> `PlaceAnOrder` standard tiktok Event through event `order completed` in payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + accessToken: 'dummyAccessToken', + hashUserProperties: true, + eventsToStandard: [], + }, + }, + message: { + type: 'track', + event: 'order completed', + sentAt: '2023-03-22T00:02:33.802Z', + traits: { + email: [ + 'efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + phone: [ + 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + }, + userId: '60241286212', + channel: 'sources', + context: { + sources: { + job_id: '2N4WuoNQpGYmCPASUvnV86QyhY4/Syncher', + version: 'v1.20.0', + job_run_id: 'cgd4a063b2fn2e1j0q90', + task_run_id: 'cgd4a063b2fn2e1j0qa0', + }, + }, + recordId: '16322', + rudderId: '5b4ed73f-69aa-4198-88d1-3d4d509acbf1', + messageId: 'cgd4b663b2fn2e1j8th0', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + phone: 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + value: 32.839999999999996, + emails: + '["efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900","078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f","","","","","","","",""]', + eventId: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + currency: 'USD', + order_id: 60241286212, + eventSetId: '7211223771099742210', + event_name: 'CompletePayment', + }, + receivedAt: '2023-03-22T00:02:33.171Z', + request_ip: '10.7.78.187', + anonymousId: '60241286212', + originalTimestamp: '2023-03-22T00:02:33.802Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/offline/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_set_id: '7211223771099742210', + event_id: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + order_id: 60241286212, + currency: 'USD', + value: 32.839999999999996, + }, + event: 'PlaceAnOrder', + partner_name: 'RudderStack', + context: { + user: { + emails: [ + '4dc75b075057df6f6b729e74a9feed1244dcf8ceb7903eaba13203f3268ae4b9', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + phone_numbers: [ + '28b7b205c2936d2ded022d2587fb2677a76e560e921b3ad615b739b0238baa5d', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + event_set_id: '7211223771099742210', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; From 8bf55d03ec350728a5d21b8925c801d2adc6833c Mon Sep 17 00:00:00 2001 From: gitcommitshow <56937085+gitcommitshow@users.noreply.github.com> Date: Fri, 16 Feb 2024 11:29:06 +0530 Subject: [PATCH 10/33] chore: add docker image change announcement in readme (#3093) * readme: add docker image change announcement * readme: format announcement header --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 2b3908dcb5..4ff1cd4b34 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +

+⚠️ Docker image for rudder-transformer has been moved to new org rudderstack/rudder-transformer +

+

+ [![codecov](https://codecov.io/gh/rudderlabs/rudder-transformer/branch/develop/graph/badge.svg?token=G24OON85SB)](https://codecov.io/gh/rudderlabs/rudder-transformer) # RudderStack Transformer From 0f01524b6f4f2f82efc21f88f8c97cb6fdaf91ea Mon Sep 17 00:00:00 2001 From: AASHISH MALIK Date: Fri, 16 Feb 2024 16:49:06 +0530 Subject: [PATCH 11/33] fix: add support of placing properties at root in af (#3082) * fix: add support of placing properties at root in af --- src/v0/destinations/af/transform.js | 15 +- .../destinations/af/processor/data.ts | 305 ++++++++++++++++++ 2 files changed, 316 insertions(+), 4 deletions(-) diff --git a/src/v0/destinations/af/transform.js b/src/v0/destinations/af/transform.js index 4d7ed7e635..57629b9483 100644 --- a/src/v0/destinations/af/transform.js +++ b/src/v0/destinations/af/transform.js @@ -113,9 +113,15 @@ function getEventValueForUnIdentifiedTrackEvent(message) { return { eventValue }; } -function getEventValueMapFromMappingJson(message, mappingJson, isMultiSupport) { +function getEventValueMapFromMappingJson(message, mappingJson, isMultiSupport, addPropertiesAtRoot) { let eventValue = {}; - set(eventValue, 'properties', message.properties); + + if (addPropertiesAtRoot) { + eventValue = message.properties; + } else { + set(eventValue, 'properties', message.properties); + } + const sourceKeys = Object.keys(mappingJson); sourceKeys.forEach((sourceKey) => { set(eventValue, mappingJson[sourceKey], get(message, sourceKey)); @@ -160,7 +166,7 @@ function processNonTrackEvents(message, eventName) { return payload; } -function processEventTypeTrack(message) { +function processEventTypeTrack(message, addPropertiesAtRoot) { let isMultiSupport = true; const evType = message.event && message.event.toLowerCase(); let category = ConfigCategory.DEFAULT; @@ -184,6 +190,7 @@ function processEventTypeTrack(message) { message, mappingConfig[category.name], isMultiSupport, + addPropertiesAtRoot, ); payload.eventName = message.event; payload.eventCurrency = message?.properties?.currency; @@ -196,7 +203,7 @@ function processSingleMessage(message, destination) { let payload; switch (messageType) { case EventType.TRACK: { - payload = processEventTypeTrack(message); + payload = processEventTypeTrack(message, destination.Config.addPropertiesAtRoot); break; } case EventType.SCREEN: { diff --git a/test/integrations/destinations/af/processor/data.ts b/test/integrations/destinations/af/processor/data.ts index 8b639f45c0..d0fd29b089 100644 --- a/test/integrations/destinations/af/processor/data.ts +++ b/test/integrations/destinations/af/processor/data.ts @@ -45,6 +45,7 @@ export const data = [ destination: { Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'appId' }, Enabled: true, + addPropertiesAtRoot: false, }, }, ], @@ -118,6 +119,7 @@ export const data = [ Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: false, }, Enabled: true, }, @@ -305,6 +307,7 @@ export const data = [ Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: false, }, Enabled: true, }, @@ -1532,4 +1535,306 @@ export const data = [ }, }, }, + { + name: 'af', + description: 'Place Properties at root level Page Call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + email: 'testhubspot2@email.com', + name: 'Test Hubspot', + anonymousId: '12345', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-GB', + ip: '0.0.0.0', + os: { name: 'android', version: '' }, + screen: { density: 2 }, + }, + type: 'page', + messageId: 'e8585d9a-7137-4223-b295-68ab1b17dad7', + originalTimestamp: '2019-10-15T09:35:31.289Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { path: '', referrer: '', search: '', title: '', url: '' }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'ef1d42390426e3f7c90ac78272e74344', + androidAppId: 'com.rudderlabs.javascript', + sharingFilter: 'all', + addPropertiesAtRoot: true, + }, + Enabled: true, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { + 'Content-Type': 'application/json', + authentication: 'ef1d42390426e3f7c90ac78272e74344', + }, + method: 'POST', + params: {}, + body: { + JSON: { + app_version_name: '1.0.0', + bundleIdentifier: 'com.rudderlabs.javascript', + customer_user_id: '12345', + eventValue: '{"path":"","referrer":"","search":"","title":"","url":""}', + eventName: 'page', + appsflyer_id: 'afUid', + os: '', + ip: '0.0.0.0', + sharing_filter: 'all', + }, + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'af', + description: 'Place properties at root level track call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { email: 'testhubspot2@email.com', name: 'Test Hubspot' }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-GB', + ip: '0.0.0.0', + os: { name: 'android', version: '' }, + screen: { density: 2 }, + }, + type: 'track', + messageId: '08829772-d991-427c-b976-b4c4f4430b4e', + originalTimestamp: '2019-10-15T09:35:31.291Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + event: 'test track event HS', + properties: { user_actual_role: 'system_admin, system_user', user_actual_id: 12345 }, + sentAt: '2019-10-14T11:15:53.296Z', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'ef1d42390426e3f7c90ac78272e74344', + androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: true, + }, + Enabled: true, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { + 'Content-Type': 'application/json', + authentication: 'ef1d42390426e3f7c90ac78272e74344', + }, + params: {}, + body: { + JSON: { + eventValue: + '{"user_actual_role":"system_admin, system_user","user_actual_id":12345}', + eventName: 'test track event HS', + customer_user_id: '12345', + ip: '0.0.0.0', + os: '', + appsflyer_id: 'afUid', + app_version_name: '1.0.0', + bundleIdentifier: 'com.rudderlabs.javascript', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'af', + description: 'Place properties at root track call with af data', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Order Completed', + sentAt: '2020-08-14T05:30:30.118Z', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + source: 'test', + app: { namespace: 'com.rudderlabs.javascript' }, + os: { name: 'android' }, + traits: { anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1' }, + library: { name: 'rudder-sdk-ruby-sync', version: '1.0.6' }, + }, + messageId: '7208bbb6-2c4e-45bb-bf5b-ad426f3593e9', + timestamp: '2020-08-14T05:30:30.118Z', + properties: { + tax: 2, + total: 27.5, + coupon: 'hasbros', + revenue: 48, + price: 25, + quantity: 2, + currency: 'ZAR', + discount: 2.5, + order_id: '50314b8e9bcf000000000000', + products: [ + { + sku: '45790-32', + url: 'https://www.example.com/product/path', + name: 'Monopoly: 3rd Edition', + price: 19, + category: 'Games', + quantity: 1, + image_url: 'https:///www.example.com/product/path.jpg', + product_id: '507f1f77bcf86cd799439011', + }, + { + sku: '46493-32', + name: 'Uno Card Game', + price: 3, + category: 'Games', + quantity: 2, + product_id: '505bd76785ebb509fc183733', + }, + ], + shipping: 3, + subtotal: 22.5, + affiliation: 'Google Store', + checkout_id: 'fksdjfsdjfisjf9sdfjsd9f', + }, + anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'abcde', + androidAppId: 'com.rudderlabs.javascript', + groupTypeTrait: 'email', + groupValueTrait: 'age', + trackProductsOnce: false, + trackRevenuePerProduct: false, + addPropertiesAtRoot: true, + }, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { 'Content-Type': 'application/json', authentication: 'abcde' }, + params: {}, + body: { + JSON: { + bundleIdentifier: 'com.rudderlabs.javascript', + eventValue: + '{"tax":2,"total":27.5,"coupon":"hasbros","revenue":48,"price":25,"quantity":2,"currency":"ZAR","discount":2.5,"order_id":"50314b8e9bcf000000000000","products":[{"sku":"45790-32","url":"https://www.example.com/product/path","name":"Monopoly: 3rd Edition","price":19,"category":"Games","quantity":1,"image_url":"https:///www.example.com/product/path.jpg","product_id":"507f1f77bcf86cd799439011"},{"sku":"46493-32","name":"Uno Card Game","price":3,"category":"Games","quantity":2,"product_id":"505bd76785ebb509fc183733"}],"shipping":3,"subtotal":22.5,"affiliation":"Google Store","checkout_id":"fksdjfsdjfisjf9sdfjsd9f","af_revenue":48,"af_price":[19,3],"af_quantity":[1,2],"af_order_id":"50314b8e9bcf000000000000","af_content_id":["507f1f77bcf86cd799439011","505bd76785ebb509fc183733"]}', + eventName: 'Order Completed', + eventCurrency: 'ZAR', + eventTime: '2020-08-14T05:30:30.118Z', + appsflyer_id: 'afUid', + }, + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; From 9683161612c7e3b9c2be95a2728f68ec7dcf69f4 Mon Sep 17 00:00:00 2001 From: Sankeerth Date: Fri, 16 Feb 2024 18:20:12 +0530 Subject: [PATCH 12/33] fix: custify user-regulation logic (#3076) --- src/v0/destinations/custify/deleteUsers.js | 55 +++--- .../destinations/custify/deleteUsers/data.ts | 160 ++++++++++++++++++ .../destinations/custify/network.ts | 115 +++++++++---- 3 files changed, 272 insertions(+), 58 deletions(-) create mode 100644 test/integrations/destinations/custify/deleteUsers/data.ts diff --git a/src/v0/destinations/custify/deleteUsers.js b/src/v0/destinations/custify/deleteUsers.js index 147fcc602c..921cf953bd 100644 --- a/src/v0/destinations/custify/deleteUsers.js +++ b/src/v0/destinations/custify/deleteUsers.js @@ -17,36 +17,41 @@ const userDeletionHandler = async (userAttributes, config) => { } const { apiKey } = config; - const { userId } = userAttributes; if (!apiKey) { throw new ConfigurationError('api key for deletion not present', 400); } - if (!userId) { - throw new InstrumentationError('User id for deletion not present', 400); - } - const requestUrl = `https://api.custify.com/people?user_id=${userId}`; - const requestOptions = { - headers: { - Authorization: `Bearer ${apiKey}`, - }, - }; + await Promise.all( + userAttributes.map(async (userAttr) => { + const { userId } = userAttr; + if (!userId) { + throw new InstrumentationError('User id for deletion not present', 400); + } + // Reference: https://docs.custify.com/#tag/People/paths/~1people/delete + const requestUrl = `https://api.custify.com/people?user_id=${userId}`; + const requestOptions = { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }; - const deletionResponse = await httpDELETE(requestUrl, requestOptions, { - destType: 'custify', - feature: 'deleteUsers', - }); - const processedDeletionRequest = processAxiosResponse(deletionResponse); - if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) { - throw new NetworkError( - JSON.stringify(processedDeletionRequest.response) || 'Error while deleting user', - processedDeletionRequest.status, - { - [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedDeletionRequest.status), - }, - deletionResponse, - ); - } + const deletionResponse = await httpDELETE(requestUrl, requestOptions, { + destType: 'custify', + feature: 'deleteUsers', + }); + const processedDeletionRequest = processAxiosResponse(deletionResponse); + if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) { + throw new NetworkError( + JSON.stringify(processedDeletionRequest.response) || 'Error while deleting user', + processedDeletionRequest.status, + { + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedDeletionRequest.status), + }, + deletionResponse, + ); + } + }), + ); return { statusCode: 200, status: 'successful' }; }; diff --git a/test/integrations/destinations/custify/deleteUsers/data.ts b/test/integrations/destinations/custify/deleteUsers/data.ts new file mode 100644 index 0000000000..3c5a461f69 --- /dev/null +++ b/test/integrations/destinations/custify/deleteUsers/data.ts @@ -0,0 +1,160 @@ +const destType = 'custify'; +const commonData = { + name: destType, + feature: 'userDeletion', + module: 'destination', + version: 'v0', +}; + +export const data = [ + { + description: 'Test 0: should fail when config is not being sent', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + ], + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'Config for deletion not present', + }, + ], + }, + }, + }, + { + description: 'Test 1: should fail when apiKey is not present in config', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder2', + }, + ], + config: { + apiToken: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'api key for deletion not present', + }, + ], + }, + }, + }, + + { + description: 'Test 2: should pass when one of the users is not present in destination', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder2', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [{ statusCode: 200, status: 'successful' }], + }, + }, + }, + + { + description: + 'Test 3: should fail when one of the users is returning with 4xx(not 404) from destination', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder3', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [{ statusCode: 400, error: '{"error":"User: rudder3 has a problem"}' }], + }, + }, + }, + + { + description: + 'Test 4: should fail when one of the userAttributes does not contain `userId`', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [{ statusCode: 400, error: 'User id for deletion not present' }], + }, + }, + }, +].map((props) => ({ ...commonData, ...props })); diff --git a/test/integrations/destinations/custify/network.ts b/test/integrations/destinations/custify/network.ts index 4af6545f9f..242f54c97b 100644 --- a/test/integrations/destinations/custify/network.ts +++ b/test/integrations/destinations/custify/network.ts @@ -1,36 +1,85 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.custify.com/company', - method: 'POST', + { + httpReq: { + url: 'https://api.custify.com/company', + method: 'POST', + }, + httpRes: { + data: { + company_id: '6', + name: 'Pizzeria Presto', + signed_up_at: '2019-05-30T12:00:00.000Z', + size: 15, + website: 'www.pizzeriapresto.com', + industry: 'Restaurant', + plan: 'Platinum', + monthly_revenue: 1234567, + churned: false, + owners_csm: 'john.doe@mail.com', + owners_account: 'john.doe@mail.com', + parent_companies: [ + { + id: '5ec50c9829d3c17c7cf455f2', + }, + { + id: '5ec50c9829d3c17c7cf457f2', + }, + ], + custom_attributes: { + restaurants: 5, + custom: 'template', }, - httpRes: { - data: { - "company_id": "6", - "name": "Pizzeria Presto", - "signed_up_at": "2019-05-30T12:00:00.000Z", - "size": 15, - "website": "www.pizzeriapresto.com", - "industry": "Restaurant", - "plan": "Platinum", - "monthly_revenue": 1234567, - "churned": false, - "owners_csm": "john.doe@mail.com", - "owners_account": "john.doe@mail.com", - "parent_companies": [ - { - "id": "5ec50c9829d3c17c7cf455f2" - }, - { - "id": "5ec50c9829d3c17c7cf457f2" - } - ], - "custom_attributes": { - "restaurants": 5, - "custom": "template" - } - }, - status: 200 - }, - } + }, + status: 200, + }, + }, + + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder1', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder1 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder2', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + error: 'User: rudder2 not found', + }, + status: 404, + }, + }, + + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder3', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + error: 'User: rudder3 has a problem', + }, + status: 400, + }, + }, ]; From ed32bf7aa8555cc704220648058c6a08515ca9b6 Mon Sep 17 00:00:00 2001 From: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Date: Sat, 17 Feb 2024 20:49:12 +0530 Subject: [PATCH 13/33] refactor: split trade desk into audience and realtime conversions destinations (#3091) * refactor: split trade desk into audience and realtime conversions destinations * fix: add headers for real time conversions * chore: apply review suggestions --- .../v2/destinations/the_trade_desk/config.js | 60 +- .../the_trade_desk/rtWorkflow.yaml | 18 +- .../the_trade_desk/transformConversion.js | 98 -- .../the_trade_desk/transformRecord.js | 18 +- .../v2/destinations/the_trade_desk/utils.js | 314 +----- .../destinations/the_trade_desk/utils.test.js | 624 +---------- .../config.js | 63 ++ .../data/TTDCommonConfig.json | 0 .../data/TTDItemConfig.json | 0 .../procWorkflow.yaml | 59 ++ .../utils.js | 315 ++++++ .../utils.test.js | 621 +++++++++++ .../the_trade_desk/networkHandler.js | 40 +- .../destinations/the_trade_desk/common.ts | 63 -- .../the_trade_desk/delivery/data.ts | 166 --- .../destinations/the_trade_desk/network.ts | 105 +- .../the_trade_desk/router/data.ts | 971 +---------------- .../common.ts | 79 ++ .../processor/data.ts | 984 ++++++++++++++++++ 19 files changed, 2151 insertions(+), 2447 deletions(-) delete mode 100644 src/cdk/v2/destinations/the_trade_desk/transformConversion.js create mode 100644 src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js rename src/cdk/v2/destinations/{the_trade_desk => the_trade_desk_real_time_conversions}/data/TTDCommonConfig.json (100%) rename src/cdk/v2/destinations/{the_trade_desk => the_trade_desk_real_time_conversions}/data/TTDItemConfig.json (100%) create mode 100644 src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js create mode 100644 src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js create mode 100644 test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts create mode 100644 test/integrations/destinations/the_trade_desk_real_time_conversions/processor/data.ts diff --git a/src/cdk/v2/destinations/the_trade_desk/config.js b/src/cdk/v2/destinations/the_trade_desk/config.js index 828bab3714..300325223b 100644 --- a/src/cdk/v2/destinations/the_trade_desk/config.js +++ b/src/cdk/v2/destinations/the_trade_desk/config.js @@ -1,6 +1,4 @@ -const { getMappingConfig } = require('../../../../v0/util'); - -const SUPPORTED_EVENT_TYPE = ['record', 'track']; +const SUPPORTED_EVENT_TYPE = ['record']; const ACTION_TYPES = ['insert', 'delete']; const DATA_PROVIDER_ID = 'rudderstack'; @@ -15,66 +13,10 @@ const DATA_SERVERS_BASE_ENDPOINTS_MAP = { china: 'https://data-cn2.adsrvr.cn', }; -// ref:- https://partner.thetradedesk.com/v3/portal/data/doc/DataConversionEventsApi -const REAL_TIME_CONVERSION_ENDPOINT = 'https://insight.adsrvr.org/track/realtimeconversion'; - -const CONVERSION_SUPPORTED_ID_TYPES = [ - 'TDID', - 'IDFA', - 'AAID', - 'DAID', - 'NAID', - 'IDL', - 'EUID', - 'UID2', -]; - -const ECOMM_EVENT_MAP = { - 'product added': { - event: 'addtocart', - rootLevelPriceSupported: true, - }, - 'order completed': { - event: 'purchase', - itemsArray: true, - revenueFieldSupported: true, - }, - 'product viewed': { - event: 'viewitem', - rootLevelPriceSupported: true, - }, - 'checkout started': { - event: 'startcheckout', - itemsArray: true, - revenueFieldSupported: true, - }, - 'cart viewed': { - event: 'viewcart', - itemsArray: true, - }, - 'product added to wishlist': { - event: 'wishlistitem', - rootLevelPriceSupported: true, - }, -}; - -const CONFIG_CATEGORIES = { - COMMON_CONFIGS: { name: 'TTDCommonConfig' }, - ITEM_CONFIGS: { name: 'TTDItemConfig' }, -}; - -const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); - module.exports = { SUPPORTED_EVENT_TYPE, ACTION_TYPES, DATA_PROVIDER_ID, MAX_REQUEST_SIZE_IN_BYTES: 2500000, DATA_SERVERS_BASE_ENDPOINTS_MAP, - CONVERSION_SUPPORTED_ID_TYPES, - CONFIG_CATEGORIES, - COMMON_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.COMMON_CONFIGS.name], - ITEM_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.ITEM_CONFIGS.name], - ECOMM_EVENT_MAP, - REAL_TIME_CONVERSION_ENDPOINT, }; diff --git a/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml b/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml index ee05ecd967..5f0476dd62 100644 --- a/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml @@ -3,20 +3,19 @@ bindings: path: ../../../../constants - name: processRecordInputs path: ./transformRecord - - name: processConversionInputs - path: ./transformConversion - name: handleRtTfSingleEventError path: ../../../../v0/util/index - name: InstrumentationError path: '@rudderstack/integrations-lib' steps: - - name: validateCommonConfig - description: | - validate common config for first party data and realtime conversion flow + - name: validateConfig template: | const config = ^[0].destination.Config + $.assertConfig(config.audienceId, "Segment name/Audience ID is not present. Aborting") $.assertConfig(config.advertiserId, "Advertiser ID is not present. Aborting") + $.assertConfig(config.advertiserSecretKey, "Advertiser Secret Key is not present. Aborting") + config.ttlInDays ? $.assertConfig(config.ttlInDays >=0 && config.ttlInDays <= 180, "TTL is out of range. Allowed values are 0 to 180 days") - name: validateInput template: | @@ -26,22 +25,17 @@ steps: template: | $.processRecordInputs(^.{.message.type === $.EventType.RECORD}[], ^[0].destination) - - name: processConversionEvents - template: | - $.processConversionInputs(^.{.message.type === $.EventType.TRACK}[]) - - name: failOtherEvents template: | - const otherEvents = ^.{.message.type !== $.EventType.TRACK && .message.type !== $.EventType.RECORD}[] + const otherEvents = ^.{.message.type !== $.EventType.RECORD}[] let failedEvents = otherEvents.map( function(event) { const error = new $.InstrumentationError("Event type " + event.message.type + " is not supported"); $.handleRtTfSingleEventError(event, error, {}) } ) - failedEvents ?? [] - name: finalPayload template: | - [...$.outputs.processRecordEvents, ...$.outputs.processConversionEvents, ...$.outputs.failOtherEvents] + [...$.outputs.processRecordEvents, ...$.outputs.failOtherEvents] diff --git a/src/cdk/v2/destinations/the_trade_desk/transformConversion.js b/src/cdk/v2/destinations/the_trade_desk/transformConversion.js deleted file mode 100644 index b282c43151..0000000000 --- a/src/cdk/v2/destinations/the_trade_desk/transformConversion.js +++ /dev/null @@ -1,98 +0,0 @@ -const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); -const { - defaultRequestConfig, - simpleProcessRouterDest, - defaultPostRequestConfig, - removeUndefinedAndNullValues, -} = require('../../../../v0/util'); -const { EventType } = require('../../../../constants'); -const { REAL_TIME_CONVERSION_ENDPOINT } = require('./config'); -const { - prepareFromConfig, - prepareCommonPayload, - getRevenue, - prepareItemsPayload, - getAdvertisingId, - prepareCustomProperties, - populateEventName, - getDataProcessingOptions, - getPrivacySetting, - enrichTrackPayload, -} = require('./utils'); - -const responseBuilder = (payload) => { - const response = defaultRequestConfig(); - response.endpoint = REAL_TIME_CONVERSION_ENDPOINT; - response.method = defaultPostRequestConfig.requestMethod; - response.body.JSON = payload; - return response; -}; - -const validateInputAndConfig = (message, destination) => { - const { Config } = destination; - if (!Config.trackerId) { - throw new ConfigurationError('Tracking Tag ID is not present. Aborting'); - } - - if (!message.type) { - throw new InstrumentationError('Event type is required'); - } - - const messageType = message.type.toLowerCase(); - if (messageType !== EventType.TRACK) { - throw new InstrumentationError(`Event type "${messageType}" is not supported`); - } - - if (!message.event) { - throw new InstrumentationError('Event name is not present. Aborting.'); - } -}; - -const prepareTrackPayload = (message, destination) => { - const configPayload = prepareFromConfig(destination); - const commonPayload = prepareCommonPayload(message); - // prepare items array - const items = prepareItemsPayload(message); - const { id, type } = getAdvertisingId(message); - // get td1-td10 custom properties - const customProperties = prepareCustomProperties(message, destination); - const eventName = populateEventName(message, destination); - const value = getRevenue(message); - let payload = { - ...configPayload, - ...commonPayload, - event_name: eventName, - value, - items, - adid: id, - adid_type: type, - ...customProperties, - data_processing_option: getDataProcessingOptions(message), - privacy_settings: getPrivacySetting(message), - }; - - payload = enrichTrackPayload(message, payload); - return { data: [removeUndefinedAndNullValues(payload)] }; -}; - -const trackResponseBuilder = (message, destination) => { - const payload = prepareTrackPayload(message, destination); - return responseBuilder(payload); -}; - -const processEvent = (message, destination) => { - validateInputAndConfig(message, destination); - return trackResponseBuilder(message, destination); -}; - -const process = (event) => processEvent(event.message, event.destination); - -const processConversionInputs = async (inputs, reqMetadata) => { - if (!inputs || inputs.length === 0) { - return []; - } - const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); - return respList; -}; - -module.exports = { processConversionInputs }; diff --git a/src/cdk/v2/destinations/the_trade_desk/transformRecord.js b/src/cdk/v2/destinations/the_trade_desk/transformRecord.js index d571e11b7a..b452f8d7bc 100644 --- a/src/cdk/v2/destinations/the_trade_desk/transformRecord.js +++ b/src/cdk/v2/destinations/the_trade_desk/transformRecord.js @@ -1,4 +1,4 @@ -const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { BatchUtils } = require('@rudderstack/workflow-engine'); const { defaultPostRequestConfig, @@ -13,20 +13,6 @@ const tradeDeskConfig = require('./config'); const { DATA_PROVIDER_ID } = tradeDeskConfig; -const validateConfig = (config) => { - if (!config.advertiserSecretKey) { - throw new ConfigurationError('Advertiser Secret Key is not present. Aborting'); - } - - if (config.ttlInDays && !(config.ttlInDays >= 0 && config.ttlInDays <= 180)) { - throw new ConfigurationError('TTL is out of range. Allowed values are 0 to 180 days'); - } - - if (!config.audienceId) { - throw new ConfigurationError('Segment name/Audience ID is not present. Aborting'); - } -}; - const responseBuilder = (items, config) => { const { advertiserId, dataServer } = config; @@ -64,8 +50,6 @@ const processRecordInputs = (inputs, destination) => { return []; } - validateConfig(Config); - const invalidActionTypeError = new InstrumentationError( 'Invalid action type. You can only add or remove IDs from the audience/segment', ); diff --git a/src/cdk/v2/destinations/the_trade_desk/utils.js b/src/cdk/v2/destinations/the_trade_desk/utils.js index f51d8dc3ff..71b479438c 100644 --- a/src/cdk/v2/destinations/the_trade_desk/utils.js +++ b/src/cdk/v2/destinations/the_trade_desk/utils.js @@ -1,28 +1,10 @@ -const lodash = require('lodash'); -const get = require('get-value'); const CryptoJS = require('crypto-js'); -const { InstrumentationError, AbortedError } = require('@rudderstack/integrations-lib'); -const { - constructPayload, - getHashFromArray, - isDefinedAndNotNull, - isAppleFamily, - getIntegrationsObj, - extractCustomFields, - generateExclusionList, -} = require('../../../../v0/util'); -const { - DATA_SERVERS_BASE_ENDPOINTS_MAP, - CONVERSION_SUPPORTED_ID_TYPES, - COMMON_CONFIGS, - ITEM_CONFIGS, - ECOMM_EVENT_MAP, -} = require('./config'); +const { AbortedError } = require('@rudderstack/integrations-lib'); +const { DATA_SERVERS_BASE_ENDPOINTS_MAP } = require('./config'); const getTTLInMin = (ttl) => parseInt(ttl, 10) * 1440; const getBaseEndpoint = (dataServer) => DATA_SERVERS_BASE_ENDPOINTS_MAP[dataServer]; const getFirstPartyEndpoint = (dataServer) => `${getBaseEndpoint(dataServer)}/data/advertiser`; -const prepareCommonPayload = (message) => constructPayload(message, COMMON_CONFIGS); /** * Generates a signature header for a given request using a secret key. @@ -41,300 +23,8 @@ const getSignatureHeader = (request, secretKey) => { return base; }; -const prepareFromConfig = (destination) => ({ - tracker_id: destination.Config?.trackerId, - adv: destination.Config?.advertiserId, -}); - -/** - * Calculates the revenue based on the given message. - * - * @param {Object} message - The message object containing the event and properties. - * @returns {number} - The calculated revenue. - * @throws {InstrumentationError} - If the event is 'Order Completed' and revenue is not provided. - */ -const getRevenue = (message) => { - const { event, properties } = message; - let revenue = properties?.value; - const eventsMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; - if (eventsMapInfo?.rootLevelPriceSupported) { - const { price, quantity = 1 } = properties; - if (price && !Number.isNaN(parseFloat(price)) && !Number.isNaN(parseInt(quantity, 10))) { - revenue = parseFloat(price) * parseInt(quantity, 10); - } - } else if (eventsMapInfo?.revenueFieldSupported) { - revenue = properties?.revenue || revenue; - if (event.toLowerCase() === 'order completed' && !revenue) { - throw new InstrumentationError('value is required for `Order Completed` event'); - } - } - - return revenue; -}; - -/** - * Generates items from properties of a given message. - * - * @param {Object} message - The message object containing properties. - * @returns {Array} - An array of items generated from the properties. - */ -const prepareItemsFromProperties = (message) => { - const { properties } = message; - const items = []; - const item = constructPayload(properties, ITEM_CONFIGS); - items.push(item); - return items; -}; - -/** - * Generates items payload from products. - * - * @param {Object} message - The message object. - * @returns {Array} - The items payload. - */ -const prepareItemsFromProducts = (message) => { - const products = get(message, 'properties.products'); - const items = []; - products.forEach((product) => { - const item = constructPayload(product, ITEM_CONFIGS); - const itemExclusionList = generateExclusionList(ITEM_CONFIGS); - extractCustomFields(product, item, 'root', itemExclusionList); - items.push(item); - }); - return items; -}; - -/** - * Generates items payload from root properties or products. - * - * @param {Object} message - The message object containing event and properties. - * @returns {Array} - The array of items payload. - */ -const prepareItemsPayload = (message) => { - const { event } = message; - let items; - const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; - if (eventMapInfo?.itemsArray) { - // if event is one of the supported ecommerce events and products array is present - items = prepareItemsFromProducts(message); - } else if (eventMapInfo) { - // if event is one of the supported ecommerce events and products array is not present - items = prepareItemsFromProperties(message); - } - return items; -}; - -/** - * Retrieves the device advertising ID and type based on the provided message. - * - * @param {Object} message - The message object containing the context. - * @returns {Object} - An object containing the device advertising ID and type. - */ -const getDeviceAdvertisingId = (message) => { - const { context } = message; - const deviceId = context?.device?.advertisingId; - const osName = context?.os?.name?.toLowerCase(); - - let type; - switch (osName) { - case 'android': - type = 'AAID'; - break; - case 'windows': - type = 'NAID'; - break; - default: - type = isAppleFamily(osName) ? 'IDFA' : undefined; - break; - } - - return { deviceId, type }; -}; - -/** - * Retrieves the external ID object from the given message context. - * - * @param {Object} message - The message object containing the context. - * @returns {Object|undefined} - The external ID object, or undefined if not found. - */ -const getDestinationExternalIDObject = (message) => { - const { context } = message; - const externalIdArray = context?.externalId || []; - - let externalIdObj; - - if (Array.isArray(externalIdArray)) { - externalIdObj = externalIdArray.find( - (extIdObj) => - CONVERSION_SUPPORTED_ID_TYPES.includes(extIdObj?.type?.toUpperCase()) && extIdObj?.id, - ); - } - return externalIdObj; -}; - -/** - * Retrieves the advertising ID and type from the given message. - * - * @param {Object} message - The message object containing the context. - * @returns {Object} - An object containing the advertising ID and type. - * If the advertising ID and type are found in the device context, they are returned. - * If not, the external ID object is checked and if found, its ID and type are returned. - * If neither the device context nor the external ID object contain the required information, - * an object with null values for ID and type is returned. - */ -const getAdvertisingId = (message) => { - const { deviceId, type } = getDeviceAdvertisingId(message); - if (deviceId && type) { - return { id: deviceId, type }; - } - const externalIdObj = getDestinationExternalIDObject(message); - if (externalIdObj?.id && externalIdObj?.type) { - return { id: externalIdObj.id, type: externalIdObj.type.toUpperCase() }; - } - - return { id: null, type: null }; -}; - -/** - * Prepares custom properties (td1-td10) for a given message and destination. - * - * @param {object} message - The message object. - * @param {object} destination - The destination object. - * @returns {object} - The prepared payload object. - */ -const prepareCustomProperties = (message, destination) => { - const { customProperties } = destination.Config; - const payload = {}; - if (customProperties) { - customProperties.forEach((customProperty) => { - const { rudderProperty, tradeDeskProperty } = customProperty; - const value = get(message, rudderProperty); - if (value) { - payload[tradeDeskProperty] = value; - // unset the rudder property from the message, since it is already mapped to a trade desk property - lodash.unset(message, rudderProperty); - } - }); - } - return payload; -}; - -/** - * Retrieves the event name based on the provided message and destination. - * - * @param {object} message - The message object containing the event. - * @param {object} destination - The destination object containing the events mapping configuration. - * @returns {string} - The event name. - */ -const populateEventName = (message, destination) => { - let eventName; - const { event } = message; - const { eventsMapping } = destination.Config; - - // if event is mapped on dashboard, use the mapped event name - if (Array.isArray(eventsMapping) && eventsMapping.length > 0) { - const keyMap = getHashFromArray(eventsMapping, 'from', 'to'); - eventName = keyMap[event.toLowerCase()]; - } - - if (eventName) { - return eventName; - } - - // if event is one of the supported ecommerce events, use the mapped trade desk event name - const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; - if (isDefinedAndNotNull(eventMapInfo)) { - return eventMapInfo.event; - } - - // else return the event name as it is - return event; -}; - -/** - * Retrieves the data processing options based on the provided message. - * - * @param {string} message - The message to process. - * @throws {InstrumentationError} - Throws an error if the region is not supported, if no policies are provided, if multiple policies are provided, or if the policy is not supported. - * @returns {Object} - The data processing options, including the policies and region. - */ -const getDataProcessingOptions = (message) => { - const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK') || {}; - let { policies } = integrationObj; - const { region } = integrationObj; - let dataProcessingOptions; - - if (region && !region.toLowerCase().startsWith('us')) { - throw new InstrumentationError('Only US states are supported'); - } - - if (!policies || (Array.isArray(policies) && policies.length === 0)) { - policies = ['LDU']; - } - - if (policies.length > 1) { - throw new InstrumentationError('Only one policy is allowed'); - } - - if (policies[0] !== 'LDU') { - throw new InstrumentationError('Only LDU policy is supported'); - } - - if (policies && region) { - dataProcessingOptions = { policies, region }; - } - - return dataProcessingOptions; -}; - -const getPrivacySetting = (message) => { - const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK'); - return integrationObj?.privacy_settings; -}; - -/** - * Enriches the track payload with extra properties present in 'properties' other than the ones defined in TTDCommonConfig.json and TTDItemConfig.json - * - * @param {Object} message - The message object containing the event information. - * @param {Object} payload - The payload object to be enriched. - * @returns {Object} - The enriched payload object. - */ -const enrichTrackPayload = (message, payload) => { - let rawPayload = { ...payload }; - const eventsMapInfo = ECOMM_EVENT_MAP[message.event.toLowerCase()]; - // checking if event is an ecomm one and itemsArray/products support is not present. e.g Product Added event - if (eventsMapInfo && !eventsMapInfo.itemsArray) { - const itemExclusionList = generateExclusionList(ITEM_CONFIGS); - rawPayload = extractCustomFields(message, rawPayload, ['properties'], itemExclusionList); - } else if (eventsMapInfo) { - // for ecomm events with products array supports. e.g Order Completed event - rawPayload = extractCustomFields( - message, - rawPayload, - ['properties'], - ['products', 'revenue', 'value'], - ); - } else { - // for custom events - rawPayload = extractCustomFields(message, rawPayload, ['properties'], ['value']); - } - return rawPayload; -}; - module.exports = { getTTLInMin, getFirstPartyEndpoint, getSignatureHeader, - prepareFromConfig, - getRevenue, - prepareCommonPayload, - prepareItemsPayload, - getDeviceAdvertisingId, - getDestinationExternalIDObject, - getAdvertisingId, - prepareCustomProperties, - populateEventName, - getDataProcessingOptions, - getPrivacySetting, - enrichTrackPayload, }; diff --git a/src/cdk/v2/destinations/the_trade_desk/utils.test.js b/src/cdk/v2/destinations/the_trade_desk/utils.test.js index 029c3004ae..81fd7cf17d 100644 --- a/src/cdk/v2/destinations/the_trade_desk/utils.test.js +++ b/src/cdk/v2/destinations/the_trade_desk/utils.test.js @@ -1,16 +1,5 @@ -const { AbortedError, InstrumentationError } = require('@rudderstack/integrations-lib'); -const { - getSignatureHeader, - getRevenue, - getDeviceAdvertisingId, - getDestinationExternalIDObject, - getAdvertisingId, - prepareCustomProperties, - populateEventName, - getDataProcessingOptions, - getPrivacySetting, - enrichTrackPayload, -} = require('./utils'); +const { AbortedError } = require('@rudderstack/integrations-lib'); +const { getSignatureHeader } = require('./utils'); describe('getSignatureHeader', () => { it('should calculate the signature header for a valid request and secret key', () => { @@ -58,612 +47,3 @@ describe('getSignatureHeader', () => { }).toThrow(AbortedError); }); }); - -describe('getRevenue', () => { - it('should return revenue value from message properties for custom events', () => { - const message = { - event: 'customEvent', - properties: { - value: 100, - }, - }; - const result = getRevenue(message); - expect(result).toBe(100); - }); - - it('should calculate revenue based on price and quantity from message properties if ecomm event is supported for price calculation', () => { - const message = { - event: 'Product Added', - properties: { - price: 10, - quantity: 5, - }, - }; - const result = getRevenue(message); - expect(result).toBe(50); - }); - - it('should return revenue value from message properties if ecomm event is supported for revenue calculation', () => { - const message = { - event: 'Order Completed', - properties: { - revenue: 200, - }, - }; - const result = getRevenue(message); - expect(result).toBe(200); - }); - - it('should return default revenue value from properties.value for ecomm events', () => { - let message = { - event: 'Product Added', - properties: { - price: '', - value: 200, - }, - }; - let result = getRevenue(message); - expect(result).toBe(200); - - message = { - event: 'Order Completed', - properties: { - value: 200, - }, - }; - result = getRevenue(message); - expect(result).toBe(200); - }); - - it('should throw an Instrumentation error if revenue is missing for `Order Completed` event', () => { - const message = { - event: 'Order Completed', - properties: {}, - }; - expect(() => { - getRevenue(message); - }).toThrow(InstrumentationError); - }); -}); - -describe('getDeviceAdvertisingId', () => { - it('should return an object with deviceId and type properties when context.device.advertisingId and context.os.name are present', () => { - let message = { - context: { - device: { - advertisingId: '123456789', - }, - os: { - name: 'android', - }, - }, - }; - let result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: '123456789', type: 'AAID' }); - - message = { - context: { - device: { - advertisingId: '123456789', - }, - os: { - name: 'ios', - }, - }, - }; - result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: '123456789', type: 'IDFA' }); - - message = { - context: { - device: { - advertisingId: '123456789', - }, - os: { - name: 'windows', - }, - }, - }; - result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: '123456789', type: 'NAID' }); - }); - - it('should return an object with undefined type property when osName is not "android", "windows", or an Apple OS', () => { - const message = { - context: { - device: { - advertisingId: '123456789', - }, - os: { - name: 'linux', - }, - }, - }; - const result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: '123456789', type: undefined }); - }); - - it('should return an object with undefined deviceId and type properties when context is undefined', () => { - let message = {}; - let result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: undefined, type: undefined }); - - message = { - context: {}, - }; - result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: undefined, type: undefined }); - - message = { - context: { - device: {}, - }, - }; - result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: undefined, type: undefined }); - }); -}); - -describe('getDestinationExternalIDObject', () => { - it('should return the external ID object when it exists in the message context', () => { - const message = { - context: { - externalId: [ - { id: '123', type: 'daid' }, - { id: '456', type: 'type123' }, - ], - }, - }; - const result = getDestinationExternalIDObject(message); - expect(result).toEqual({ id: '123', type: 'daid' }); - }); - - it('should return undefined when no external ID object exists in the message context', () => { - let message = { - context: { - externalId: [], - }, - }; - let result = getDestinationExternalIDObject(message); - expect(result).toBeUndefined(); - - message = { - context: {}, - }; - result = getDestinationExternalIDObject(message); - expect(result).toBeUndefined(); - }); - - it('should return the first matching external ID object in the array', () => { - const message = { - context: { - externalId: [ - { id: '', type: 'daid' }, - { id: '456', type: 'tdid' }, - { id: '789', type: 'UID2' }, - ], - }, - }; - const result = getDestinationExternalIDObject(message); - expect(result).toEqual({ id: '456', type: 'tdid' }); - }); -}); - -describe('getAdvertisingId', () => { - it('should return an object with the ID and type when the message contains a valid device advertising ID and OS type', () => { - const message = { - context: { - device: { - advertisingId: '1234567890', - }, - os: { - name: 'android', - }, - }, - }; - - const result = getAdvertisingId(message); - expect(result).toEqual({ id: '1234567890', type: 'AAID' }); - }); - - it('should return an object with the ID and type when the message contains a valid external ID object with a supported type', () => { - const message = { - context: { - externalId: [ - { - type: 'IDFA', - id: 'abcdefg', - }, - ], - }, - }; - - const result = getAdvertisingId(message); - expect(result).toEqual({ id: 'abcdefg', type: 'IDFA' }); - }); - - it('should return an object with undefined ID and type when the message contains a valid external ID object with an unsupported type', () => { - let message = { - context: { - externalId: [ - { - type: 'unsupported', - id: '1234567890', - }, - ], - }, - }; - - let result = getAdvertisingId(message); - expect(result).toEqual({ id: null, type: null }); - - message = { - context: { - device: { - advertisingId: '1234567890', - }, - }, - }; - - result = getAdvertisingId(message); - expect(result).toEqual({ id: null, type: null }); - }); - - it('should return an object with undefined ID and type when the message contains an external ID object with a supported type but no ID or missing externalId', () => { - let message = { - context: { - externalId: [ - { - type: 'IDFA', - }, - ], - }, - }; - let result = getAdvertisingId(message); - expect(result).toEqual({ id: null, type: null }); - - message = { - context: {}, - }; - result = getAdvertisingId(message); - expect(result).toEqual({ id: null, type: null }); - }); -}); - -describe('prepareCustomProperties', () => { - it('should return an empty object when customProperties is an empty array', () => { - const message = {}; - let destination = { Config: { customProperties: [] } }; - let result = prepareCustomProperties(message, destination); - expect(result).toEqual({}); - - destination = { Config: { customProperties: [{ rudderProperty: '', tradeDeskProperty: '' }] } }; - result = prepareCustomProperties(message, destination); - expect(result).toEqual({}); - - destination = { Config: { customProperties: undefined } }; - result = prepareCustomProperties(message, destination); - expect(result).toEqual({}); - }); - - it('should return an object with `tradeDeskProperty` as key and `rudderProperty` value as value when `rudderProperty` exists in message', () => { - const message = { - rudderProperty1: 'value1', - rudderProperty2: 'value2', - }; - const destination = { - Config: { - customProperties: [ - { - rudderProperty: 'rudderProperty1', - tradeDeskProperty: 'tradeDeskProperty1', - }, - { - rudderProperty: 'rudderProperty2', - tradeDeskProperty: 'tradeDeskProperty2', - }, - { - rudderProperty: 'rudderProperty3', - tradeDeskProperty: 'tradeDeskProperty3', - }, - ], - }, - }; - const result = prepareCustomProperties(message, destination); - expect(result).toEqual({ - tradeDeskProperty1: 'value1', - tradeDeskProperty2: 'value2', - }); - }); -}); - -describe('populateEventName', () => { - it('should return the eventName if it exists in the eventsMapping of destination.Config', () => { - const message = { event: 'someEvent' }; - const destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } }; - const result = populateEventName(message, destination); - expect(result).toBe('mappedEvent'); - }); - - it('should return the eventName if it exists in the ECOMM_EVENT_MAP', () => { - const message = { event: 'product added' }; - let destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } }; - let result = populateEventName(message, destination); - expect(result).toBe('addtocart'); - - destination = { Config: { eventsMapping: [] } }; - result = populateEventName(message, destination); - expect(result).toBe('addtocart'); - }); - - it('should return undefined if eventsMapping is an empty array', () => { - const message = { event: 'someEvent' }; - const destination = { Config: { eventsMapping: [] } }; - const result = populateEventName(message, destination); - expect(result).toBe('someEvent'); - }); -}); - -describe('getDataProcessingOptions', () => { - it('should return an object with policies and region when provided a integrationObj in message', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: ['LDU'], - region: 'US-CO', - }, - }, - }; - const expected = { - policies: ['LDU'], - region: 'US-CO', - }; - const result = getDataProcessingOptions(message); - expect(result).toEqual(expected); - }); - - it('should throw an InstrumentationError if the region is not a US state', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: ['LDU'], - region: 'EU-abc', - }, - }, - }; - expect(() => { - getDataProcessingOptions(message); - }).toThrow(InstrumentationError); - }); - - it('should throw an InstrumentationError if multiple policies are provided', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: ['LDU', 'Policy2'], - region: 'US-CO', - }, - }, - }; - - expect(() => { - getDataProcessingOptions(message); - }).toThrow(InstrumentationError); - }); - - it('should throw an InstrumentationError if a policy other than `LDU` is provided', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: ['Policy1'], - region: 'US-CO', - }, - }, - }; - - expect(() => { - getDataProcessingOptions(message); - }).toThrow(InstrumentationError); - }); - - it('should return an object with default policy `LDU` when policies are not provided', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: [], - region: 'US-CO', - }, - }, - }; - - const expected = { - policies: ['LDU'], - region: 'US-CO', - }; - - expect(getDataProcessingOptions(message)).toEqual(expected); - }); - - it('should handle empty cases', () => { - let message = { - integrations: { - All: true, - THE_TRADE_DESK: {}, - }, - }; - - expect(getDataProcessingOptions(message)).toBeUndefined(); - - message = { - integrations: { - All: true, - }, - }; - - expect(getDataProcessingOptions(message)).toBeUndefined(); - - message = { - integrations: { - All: true, - THE_TRADE_DESK: { region: 'US-CO' }, - }, - }; - - expect(getDataProcessingOptions(message)).toEqual({ policies: ['LDU'], region: 'US-CO' }); - }); -}); - -describe('getPrivacySetting', () => { - it('should return the privacy settings object when it exists in the integration object', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - privacy_settings: [ - { - privacy_type: 'GDPR', - is_applicable: 1, - consent_string: 'ok', - }, - ], - }, - }, - }; - const expected = [ - { - privacy_type: 'GDPR', - is_applicable: 1, - consent_string: 'ok', - }, - ]; - const result = getPrivacySetting(message); - expect(result).toEqual(expected); - }); - - it('should return null when the privacy settings object does not exist in the integration object', () => { - let message = { integrations: {} }; - expect(getPrivacySetting(message)).toBeUndefined(); - - message = { integrations: { THE_TRADE_DESK: {} } }; - expect(getPrivacySetting(message)).toBeUndefined(); - - message = { integrations: { THE_TRADE_DESK: { privacy_settings: null } } }; - expect(getPrivacySetting(message)).toBeNull(); - }); -}); - -describe('enrichTrackPayload', () => { - it('should correctly enrich the payload with custom fields for ecomm events where product array is not supported', () => { - const message = { - event: 'Product Added', - properties: { - product_id: 'prd123', - sku: 'sku123', - brand: 'brand123', - property1: 'value1', - property2: 'value2', - }, - }; - const payload = { - items: [ - { - item_code: 'prd123', - }, - ], - property1: 'value1', - property2: 'value2', - }; - const expectedPayload = { - items: [ - { - item_code: 'prd123', - }, - ], - brand: 'brand123', - property1: 'value1', - property2: 'value2', - }; - - const result = enrichTrackPayload(message, payload); - expect(result).toEqual(expectedPayload); - }); - - it('should correctly enrich the payload with custom fields when the for ecomm events with products array support', () => { - const message = { - event: 'order completed', - properties: { - order_id: 'ord123', - total: 52.0, - subtotal: 45.0, - revenue: 50.0, - products: [{ product_id: 'prd123', sku: 'sku123', brand: 'brand123' }], - property1: 'value1', - property2: 'value2', - }, - }; - const payload = { - order_id: 'ord123', - value: 50.0, - items: [{ item_code: 'prd123', brand: 'brand123' }], - property1: 'value1', - property2: 'value2', - }; - const expectedPayload = { - order_id: 'ord123', - total: 52.0, - subtotal: 45.0, - value: 50.0, - items: [{ item_code: 'prd123', brand: 'brand123' }], - property1: 'value1', - property2: 'value2', - }; - - const result = enrichTrackPayload(message, payload); - - expect(result).toEqual(expectedPayload); - }); - - it('should return the enriched payload for custom event', () => { - const message = { - event: 'someEvent', - properties: { - order_id: 'ord123', - property1: 'value1', - property2: 'value2', - revenue: 10, - value: 11, - products: [ - { - product_id: 'prd123', - test: 'test', - }, - ], - }, - }; - const payload = { - order_id: 'ord123', - value: 11, - }; - const expectedPayload = { - order_id: 'ord123', - property1: 'value1', - property2: 'value2', - revenue: 10, - value: 11, - products: [ - { - product_id: 'prd123', - test: 'test', - }, - ], - }; - - const result = enrichTrackPayload(message, payload); - expect(result).toEqual(expectedPayload); - }); -}); diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js new file mode 100644 index 0000000000..7af732185f --- /dev/null +++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js @@ -0,0 +1,63 @@ +const { getMappingConfig } = require('../../../../v0/util'); + +const SUPPORTED_EVENT_TYPE = ['track']; + +// ref:- https://partner.thetradedesk.com/v3/portal/data/doc/DataConversionEventsApi +const REAL_TIME_CONVERSION_ENDPOINT = 'https://insight.adsrvr.org/track/realtimeconversion'; + +const CONVERSION_SUPPORTED_ID_TYPES = [ + 'TDID', + 'IDFA', + 'AAID', + 'DAID', + 'NAID', + 'IDL', + 'EUID', + 'UID2', +]; + +const ECOMM_EVENT_MAP = { + 'product added': { + event: 'addtocart', + rootLevelPriceSupported: true, + }, + 'order completed': { + event: 'purchase', + itemsArray: true, + revenueFieldSupported: true, + }, + 'product viewed': { + event: 'viewitem', + rootLevelPriceSupported: true, + }, + 'checkout started': { + event: 'startcheckout', + itemsArray: true, + revenueFieldSupported: true, + }, + 'cart viewed': { + event: 'viewcart', + itemsArray: true, + }, + 'product added to wishlist': { + event: 'wishlistitem', + rootLevelPriceSupported: true, + }, +}; + +const CONFIG_CATEGORIES = { + COMMON_CONFIGS: { name: 'TTDCommonConfig' }, + ITEM_CONFIGS: { name: 'TTDItemConfig' }, +}; + +const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); + +module.exports = { + SUPPORTED_EVENT_TYPE, + CONFIG_CATEGORIES, + CONVERSION_SUPPORTED_ID_TYPES, + COMMON_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.COMMON_CONFIGS.name], + ITEM_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.ITEM_CONFIGS.name], + ECOMM_EVENT_MAP, + REAL_TIME_CONVERSION_ENDPOINT, +}; diff --git a/src/cdk/v2/destinations/the_trade_desk/data/TTDCommonConfig.json b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDCommonConfig.json similarity index 100% rename from src/cdk/v2/destinations/the_trade_desk/data/TTDCommonConfig.json rename to src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDCommonConfig.json diff --git a/src/cdk/v2/destinations/the_trade_desk/data/TTDItemConfig.json b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json similarity index 100% rename from src/cdk/v2/destinations/the_trade_desk/data/TTDItemConfig.json rename to src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml new file mode 100644 index 0000000000..5191320cdc --- /dev/null +++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml @@ -0,0 +1,59 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + - name: defaultRequestConfig + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - path: ./config + exportAll: true + - path: ./utils + exportAll: true + +steps: + - name: validateConfig + template: | + $.assertConfig(.destination.Config.advertiserId, "Advertiser ID is not present. Aborting") + $.assertConfig(.destination.Config.trackerId, "Tracking Tag ID is not present. Aborting") + + - name: validateInput + template: | + let messageType = .message.type; + $.assert(messageType, "message Type is not present. Aborting."); + $.assert(messageType.toLowerCase() === $.EventType.TRACK, "Event type " + messageType + " is not supported"); + $.assert(.message.event, "Event is not present. Aborting."); + + - name: prepareTrackPayload + template: | + const configPayload = $.prepareFromConfig(.destination); + const commonPayload = $.prepareCommonPayload(.message); + const { id, type } = $.getAdvertisingId(.message); + const items = $.prepareItemsPayload(.message); + const customProperties = $.prepareCustomProperties(.message, .destination); + const eventName = $.populateEventName(.message, .destination); + const value = $.getRevenue(.message); + let payload = { + ...configPayload, + ...commonPayload, + event_name: eventName, + value, + items, + adid: id, + adid_type: type, + ...customProperties, + data_processing_option: $.getDataProcessingOptions(.message), + privacy_settings: $.getPrivacySetting(.message), + }; + payload = $.enrichTrackPayload(.message, payload); + payload; + + - name: buildResponseForProcessTransformation + template: | + const response = $.defaultRequestConfig(); + response.body.JSON = {data: [$.removeUndefinedAndNullValues($.outputs.prepareTrackPayload)]}; + response.endpoint = $.REAL_TIME_CONVERSION_ENDPOINT; + response.headers = { + "Content-Type": "application/json" + }; + response; diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js new file mode 100644 index 0000000000..2232be61f0 --- /dev/null +++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js @@ -0,0 +1,315 @@ +const lodash = require('lodash'); +const get = require('get-value'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { + constructPayload, + getHashFromArray, + isDefinedAndNotNull, + isAppleFamily, + getIntegrationsObj, + extractCustomFields, + generateExclusionList, +} = require('../../../../v0/util'); +const { + CONVERSION_SUPPORTED_ID_TYPES, + COMMON_CONFIGS, + ITEM_CONFIGS, + ECOMM_EVENT_MAP, +} = require('./config'); + +const prepareCommonPayload = (message) => constructPayload(message, COMMON_CONFIGS); + +const prepareFromConfig = (destination) => ({ + tracker_id: destination.Config?.trackerId, + adv: destination.Config?.advertiserId, +}); + +/** + * Calculates the revenue based on the given message. + * + * @param {Object} message - The message object containing the event and properties. + * @returns {number} - The calculated revenue. + * @throws {InstrumentationError} - If the event is 'Order Completed' and revenue is not provided. + */ +const getRevenue = (message) => { + const { event, properties } = message; + let revenue = properties?.value; + const eventsMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; + if (eventsMapInfo?.rootLevelPriceSupported) { + const { price, quantity = 1 } = properties; + if (price && !Number.isNaN(parseFloat(price)) && !Number.isNaN(parseInt(quantity, 10))) { + revenue = parseFloat(price) * parseInt(quantity, 10); + } + } else if (eventsMapInfo?.revenueFieldSupported) { + revenue = properties?.revenue || revenue; + if (event.toLowerCase() === 'order completed' && !revenue) { + throw new InstrumentationError('value is required for `Order Completed` event'); + } + } + + return revenue; +}; + +/** + * Generates items from properties of a given message. + * + * @param {Object} message - The message object containing properties. + * @returns {Array} - An array of items generated from the properties. + */ +const prepareItemsFromProperties = (message) => { + const { properties } = message; + const items = []; + const item = constructPayload(properties, ITEM_CONFIGS); + items.push(item); + return items; +}; + +/** + * Generates items payload from products. + * + * @param {Object} message - The message object. + * @returns {Array} - The items payload. + */ +const prepareItemsFromProducts = (message) => { + const products = get(message, 'properties.products'); + const items = []; + products.forEach((product) => { + const item = constructPayload(product, ITEM_CONFIGS); + const itemExclusionList = generateExclusionList(ITEM_CONFIGS); + extractCustomFields(product, item, 'root', itemExclusionList); + items.push(item); + }); + return items; +}; + +/** + * Generates items payload from root properties or products. + * + * @param {Object} message - The message object containing event and properties. + * @returns {Array} - The array of items payload. + */ +const prepareItemsPayload = (message) => { + const { event } = message; + let items; + const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; + if (eventMapInfo?.itemsArray) { + // if event is one of the supported ecommerce events and products array is present + items = prepareItemsFromProducts(message); + } else if (eventMapInfo) { + // if event is one of the supported ecommerce events and products array is not present + items = prepareItemsFromProperties(message); + } + return items; +}; + +/** + * Retrieves the device advertising ID and type based on the provided message. + * + * @param {Object} message - The message object containing the context. + * @returns {Object} - An object containing the device advertising ID and type. + */ +const getDeviceAdvertisingId = (message) => { + const { context } = message; + const deviceId = context?.device?.advertisingId; + const osName = context?.os?.name?.toLowerCase(); + + let type; + switch (osName) { + case 'android': + type = 'AAID'; + break; + case 'windows': + type = 'NAID'; + break; + default: + type = isAppleFamily(osName) ? 'IDFA' : undefined; + break; + } + + return { deviceId, type }; +}; + +/** + * Retrieves the external ID object from the given message context. + * + * @param {Object} message - The message object containing the context. + * @returns {Object|undefined} - The external ID object, or undefined if not found. + */ +const getDestinationExternalIDObject = (message) => { + const { context } = message; + const externalIdArray = context?.externalId || []; + + let externalIdObj; + + if (Array.isArray(externalIdArray)) { + externalIdObj = externalIdArray.find( + (extIdObj) => + CONVERSION_SUPPORTED_ID_TYPES.includes(extIdObj?.type?.toUpperCase()) && extIdObj?.id, + ); + } + return externalIdObj; +}; + +/** + * Retrieves the advertising ID and type from the given message. + * + * @param {Object} message - The message object containing the context. + * @returns {Object} - An object containing the advertising ID and type. + * If the advertising ID and type are found in the device context, they are returned. + * If not, the external ID object is checked and if found, its ID and type are returned. + * If neither the device context nor the external ID object contain the required information, + * an object with null values for ID and type is returned. + */ +const getAdvertisingId = (message) => { + const { deviceId, type } = getDeviceAdvertisingId(message); + if (deviceId && type) { + return { id: deviceId, type }; + } + const externalIdObj = getDestinationExternalIDObject(message); + if (externalIdObj?.id && externalIdObj?.type) { + return { id: externalIdObj.id, type: externalIdObj.type.toUpperCase() }; + } + + return { id: null, type: null }; +}; + +/** + * Prepares custom properties (td1-td10) for a given message and destination. + * + * @param {object} message - The message object. + * @param {object} destination - The destination object. + * @returns {object} - The prepared payload object. + */ +const prepareCustomProperties = (message, destination) => { + const { customProperties } = destination.Config; + const payload = {}; + if (customProperties) { + customProperties.forEach((customProperty) => { + const { rudderProperty, tradeDeskProperty } = customProperty; + const value = get(message, rudderProperty); + if (value) { + payload[tradeDeskProperty] = value; + // unset the rudder property from the message, since it is already mapped to a trade desk property + lodash.unset(message, rudderProperty); + } + }); + } + return payload; +}; + +/** + * Retrieves the event name based on the provided message and destination. + * + * @param {object} message - The message object containing the event. + * @param {object} destination - The destination object containing the events mapping configuration. + * @returns {string} - The event name. + */ +const populateEventName = (message, destination) => { + let eventName; + const { event } = message; + const { eventsMapping } = destination.Config; + + // if event is mapped on dashboard, use the mapped event name + if (Array.isArray(eventsMapping) && eventsMapping.length > 0) { + const keyMap = getHashFromArray(eventsMapping, 'from', 'to'); + eventName = keyMap[event.toLowerCase()]; + } + + if (eventName) { + return eventName; + } + + // if event is one of the supported ecommerce events, use the mapped trade desk event name + const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; + if (isDefinedAndNotNull(eventMapInfo)) { + return eventMapInfo.event; + } + + // else return the event name as it is + return event; +}; + +/** + * Retrieves the data processing options based on the provided message. + * + * @param {string} message - The message to process. + * @throws {InstrumentationError} - Throws an error if the region is not supported, if no policies are provided, if multiple policies are provided, or if the policy is not supported. + * @returns {Object} - The data processing options, including the policies and region. + */ +const getDataProcessingOptions = (message) => { + const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK') || {}; + let { policies } = integrationObj; + const { region } = integrationObj; + let dataProcessingOptions; + + if (region && !region.toLowerCase().startsWith('us')) { + throw new InstrumentationError('Only US states are supported'); + } + + if (!policies || (Array.isArray(policies) && policies.length === 0)) { + policies = ['LDU']; + } + + if (policies.length > 1) { + throw new InstrumentationError('Only one policy is allowed'); + } + + if (policies[0] !== 'LDU') { + throw new InstrumentationError('Only LDU policy is supported'); + } + + if (policies && region) { + dataProcessingOptions = { policies, region }; + } + + return dataProcessingOptions; +}; + +const getPrivacySetting = (message) => { + const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK'); + return integrationObj?.privacy_settings; +}; + +/** + * Enriches the track payload with extra properties present in 'properties' other than the ones defined in TTDCommonConfig.json and TTDItemConfig.json + * + * @param {Object} message - The message object containing the event information. + * @param {Object} payload - The payload object to be enriched. + * @returns {Object} - The enriched payload object. + */ +const enrichTrackPayload = (message, payload) => { + let rawPayload = { ...payload }; + const eventsMapInfo = ECOMM_EVENT_MAP[message.event.toLowerCase()]; + // checking if event is an ecomm one and itemsArray/products support is not present. e.g Product Added event + if (eventsMapInfo && !eventsMapInfo.itemsArray) { + const itemExclusionList = generateExclusionList(ITEM_CONFIGS); + rawPayload = extractCustomFields(message, rawPayload, ['properties'], itemExclusionList); + } else if (eventsMapInfo) { + // for ecomm events with products array supports. e.g Order Completed event + rawPayload = extractCustomFields( + message, + rawPayload, + ['properties'], + ['products', 'revenue', 'value'], + ); + } else { + // for custom events + rawPayload = extractCustomFields(message, rawPayload, ['properties'], ['value']); + } + return rawPayload; +}; + +module.exports = { + prepareFromConfig, + getRevenue, + prepareCommonPayload, + prepareItemsPayload, + getDeviceAdvertisingId, + getDestinationExternalIDObject, + getAdvertisingId, + prepareCustomProperties, + populateEventName, + getDataProcessingOptions, + getPrivacySetting, + enrichTrackPayload, +}; diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js new file mode 100644 index 0000000000..20b39fab93 --- /dev/null +++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js @@ -0,0 +1,621 @@ +const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { + getRevenue, + getDeviceAdvertisingId, + getDestinationExternalIDObject, + getAdvertisingId, + prepareCustomProperties, + populateEventName, + getDataProcessingOptions, + getPrivacySetting, + enrichTrackPayload, +} = require('./utils'); + +describe('getRevenue', () => { + it('should return revenue value from message properties for custom events', () => { + const message = { + event: 'customEvent', + properties: { + value: 100, + }, + }; + const result = getRevenue(message); + expect(result).toBe(100); + }); + + it('should calculate revenue based on price and quantity from message properties if ecomm event is supported for price calculation', () => { + const message = { + event: 'Product Added', + properties: { + price: 10, + quantity: 5, + }, + }; + const result = getRevenue(message); + expect(result).toBe(50); + }); + + it('should return revenue value from message properties if ecomm event is supported for revenue calculation', () => { + const message = { + event: 'Order Completed', + properties: { + revenue: 200, + }, + }; + const result = getRevenue(message); + expect(result).toBe(200); + }); + + it('should return default revenue value from properties.value for ecomm events', () => { + let message = { + event: 'Product Added', + properties: { + price: '', + value: 200, + }, + }; + let result = getRevenue(message); + expect(result).toBe(200); + + message = { + event: 'Order Completed', + properties: { + value: 200, + }, + }; + result = getRevenue(message); + expect(result).toBe(200); + }); + + it('should throw an Instrumentation error if revenue is missing for `Order Completed` event', () => { + const message = { + event: 'Order Completed', + properties: {}, + }; + expect(() => { + getRevenue(message); + }).toThrow(InstrumentationError); + }); +}); + +describe('getDeviceAdvertisingId', () => { + it('should return an object with deviceId and type properties when context.device.advertisingId and context.os.name are present', () => { + let message = { + context: { + device: { + advertisingId: '123456789', + }, + os: { + name: 'android', + }, + }, + }; + let result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: '123456789', type: 'AAID' }); + + message = { + context: { + device: { + advertisingId: '123456789', + }, + os: { + name: 'ios', + }, + }, + }; + result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: '123456789', type: 'IDFA' }); + + message = { + context: { + device: { + advertisingId: '123456789', + }, + os: { + name: 'windows', + }, + }, + }; + result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: '123456789', type: 'NAID' }); + }); + + it('should return an object with undefined type property when osName is not "android", "windows", or an Apple OS', () => { + const message = { + context: { + device: { + advertisingId: '123456789', + }, + os: { + name: 'linux', + }, + }, + }; + const result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: '123456789', type: undefined }); + }); + + it('should return an object with undefined deviceId and type properties when context is undefined', () => { + let message = {}; + let result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: undefined, type: undefined }); + + message = { + context: {}, + }; + result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: undefined, type: undefined }); + + message = { + context: { + device: {}, + }, + }; + result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: undefined, type: undefined }); + }); +}); + +describe('getDestinationExternalIDObject', () => { + it('should return the external ID object when it exists in the message context', () => { + const message = { + context: { + externalId: [ + { id: '123', type: 'daid' }, + { id: '456', type: 'type123' }, + ], + }, + }; + const result = getDestinationExternalIDObject(message); + expect(result).toEqual({ id: '123', type: 'daid' }); + }); + + it('should return undefined when no external ID object exists in the message context', () => { + let message = { + context: { + externalId: [], + }, + }; + let result = getDestinationExternalIDObject(message); + expect(result).toBeUndefined(); + + message = { + context: {}, + }; + result = getDestinationExternalIDObject(message); + expect(result).toBeUndefined(); + }); + + it('should return the first matching external ID object in the array', () => { + const message = { + context: { + externalId: [ + { id: '', type: 'daid' }, + { id: '456', type: 'tdid' }, + { id: '789', type: 'UID2' }, + ], + }, + }; + const result = getDestinationExternalIDObject(message); + expect(result).toEqual({ id: '456', type: 'tdid' }); + }); +}); + +describe('getAdvertisingId', () => { + it('should return an object with the ID and type when the message contains a valid device advertising ID and OS type', () => { + const message = { + context: { + device: { + advertisingId: '1234567890', + }, + os: { + name: 'android', + }, + }, + }; + + const result = getAdvertisingId(message); + expect(result).toEqual({ id: '1234567890', type: 'AAID' }); + }); + + it('should return an object with the ID and type when the message contains a valid external ID object with a supported type', () => { + const message = { + context: { + externalId: [ + { + type: 'IDFA', + id: 'abcdefg', + }, + ], + }, + }; + + const result = getAdvertisingId(message); + expect(result).toEqual({ id: 'abcdefg', type: 'IDFA' }); + }); + + it('should return an object with undefined ID and type when the message contains a valid external ID object with an unsupported type', () => { + let message = { + context: { + externalId: [ + { + type: 'unsupported', + id: '1234567890', + }, + ], + }, + }; + + let result = getAdvertisingId(message); + expect(result).toEqual({ id: null, type: null }); + + message = { + context: { + device: { + advertisingId: '1234567890', + }, + }, + }; + + result = getAdvertisingId(message); + expect(result).toEqual({ id: null, type: null }); + }); + + it('should return an object with undefined ID and type when the message contains an external ID object with a supported type but no ID or missing externalId', () => { + let message = { + context: { + externalId: [ + { + type: 'IDFA', + }, + ], + }, + }; + let result = getAdvertisingId(message); + expect(result).toEqual({ id: null, type: null }); + + message = { + context: {}, + }; + result = getAdvertisingId(message); + expect(result).toEqual({ id: null, type: null }); + }); +}); + +describe('prepareCustomProperties', () => { + it('should return an empty object when customProperties is an empty array', () => { + const message = {}; + let destination = { Config: { customProperties: [] } }; + let result = prepareCustomProperties(message, destination); + expect(result).toEqual({}); + + destination = { Config: { customProperties: [{ rudderProperty: '', tradeDeskProperty: '' }] } }; + result = prepareCustomProperties(message, destination); + expect(result).toEqual({}); + + destination = { Config: { customProperties: undefined } }; + result = prepareCustomProperties(message, destination); + expect(result).toEqual({}); + }); + + it('should return an object with `tradeDeskProperty` as key and `rudderProperty` value as value when `rudderProperty` exists in message', () => { + const message = { + rudderProperty1: 'value1', + rudderProperty2: 'value2', + }; + const destination = { + Config: { + customProperties: [ + { + rudderProperty: 'rudderProperty1', + tradeDeskProperty: 'tradeDeskProperty1', + }, + { + rudderProperty: 'rudderProperty2', + tradeDeskProperty: 'tradeDeskProperty2', + }, + { + rudderProperty: 'rudderProperty3', + tradeDeskProperty: 'tradeDeskProperty3', + }, + ], + }, + }; + const result = prepareCustomProperties(message, destination); + expect(result).toEqual({ + tradeDeskProperty1: 'value1', + tradeDeskProperty2: 'value2', + }); + }); +}); + +describe('populateEventName', () => { + it('should return the eventName if it exists in the eventsMapping of destination.Config', () => { + const message = { event: 'someEvent' }; + const destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } }; + const result = populateEventName(message, destination); + expect(result).toBe('mappedEvent'); + }); + + it('should return the eventName if it exists in the ECOMM_EVENT_MAP', () => { + const message = { event: 'product added' }; + let destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } }; + let result = populateEventName(message, destination); + expect(result).toBe('addtocart'); + + destination = { Config: { eventsMapping: [] } }; + result = populateEventName(message, destination); + expect(result).toBe('addtocart'); + }); + + it('should return undefined if eventsMapping is an empty array', () => { + const message = { event: 'someEvent' }; + const destination = { Config: { eventsMapping: [] } }; + const result = populateEventName(message, destination); + expect(result).toBe('someEvent'); + }); +}); + +describe('getDataProcessingOptions', () => { + it('should return an object with policies and region when provided a integrationObj in message', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: ['LDU'], + region: 'US-CO', + }, + }, + }; + const expected = { + policies: ['LDU'], + region: 'US-CO', + }; + const result = getDataProcessingOptions(message); + expect(result).toEqual(expected); + }); + + it('should throw an InstrumentationError if the region is not a US state', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: ['LDU'], + region: 'EU-abc', + }, + }, + }; + expect(() => { + getDataProcessingOptions(message); + }).toThrow(InstrumentationError); + }); + + it('should throw an InstrumentationError if multiple policies are provided', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: ['LDU', 'Policy2'], + region: 'US-CO', + }, + }, + }; + + expect(() => { + getDataProcessingOptions(message); + }).toThrow(InstrumentationError); + }); + + it('should throw an InstrumentationError if a policy other than `LDU` is provided', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: ['Policy1'], + region: 'US-CO', + }, + }, + }; + + expect(() => { + getDataProcessingOptions(message); + }).toThrow(InstrumentationError); + }); + + it('should return an object with default policy `LDU` when policies are not provided', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: [], + region: 'US-CO', + }, + }, + }; + + const expected = { + policies: ['LDU'], + region: 'US-CO', + }; + + expect(getDataProcessingOptions(message)).toEqual(expected); + }); + + it('should handle empty cases', () => { + let message = { + integrations: { + All: true, + THE_TRADE_DESK: {}, + }, + }; + + expect(getDataProcessingOptions(message)).toBeUndefined(); + + message = { + integrations: { + All: true, + }, + }; + + expect(getDataProcessingOptions(message)).toBeUndefined(); + + message = { + integrations: { + All: true, + THE_TRADE_DESK: { region: 'US-CO' }, + }, + }; + + expect(getDataProcessingOptions(message)).toEqual({ policies: ['LDU'], region: 'US-CO' }); + }); +}); + +describe('getPrivacySetting', () => { + it('should return the privacy settings object when it exists in the integration object', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + privacy_settings: [ + { + privacy_type: 'GDPR', + is_applicable: 1, + consent_string: 'ok', + }, + ], + }, + }, + }; + const expected = [ + { + privacy_type: 'GDPR', + is_applicable: 1, + consent_string: 'ok', + }, + ]; + const result = getPrivacySetting(message); + expect(result).toEqual(expected); + }); + + it('should return null when the privacy settings object does not exist in the integration object', () => { + let message = { integrations: {} }; + expect(getPrivacySetting(message)).toBeUndefined(); + + message = { integrations: { THE_TRADE_DESK: {} } }; + expect(getPrivacySetting(message)).toBeUndefined(); + + message = { integrations: { THE_TRADE_DESK: { privacy_settings: null } } }; + expect(getPrivacySetting(message)).toBeNull(); + }); +}); + +describe('enrichTrackPayload', () => { + it('should correctly enrich the payload with custom fields for ecomm events where product array is not supported', () => { + const message = { + event: 'Product Added', + properties: { + product_id: 'prd123', + sku: 'sku123', + brand: 'brand123', + property1: 'value1', + property2: 'value2', + }, + }; + const payload = { + items: [ + { + item_code: 'prd123', + }, + ], + property1: 'value1', + property2: 'value2', + }; + const expectedPayload = { + items: [ + { + item_code: 'prd123', + }, + ], + brand: 'brand123', + property1: 'value1', + property2: 'value2', + }; + + const result = enrichTrackPayload(message, payload); + expect(result).toEqual(expectedPayload); + }); + + it('should correctly enrich the payload with custom fields when the for ecomm events with products array support', () => { + const message = { + event: 'order completed', + properties: { + order_id: 'ord123', + total: 52.0, + subtotal: 45.0, + revenue: 50.0, + products: [{ product_id: 'prd123', sku: 'sku123', brand: 'brand123' }], + property1: 'value1', + property2: 'value2', + }, + }; + const payload = { + order_id: 'ord123', + value: 50.0, + items: [{ item_code: 'prd123', brand: 'brand123' }], + property1: 'value1', + property2: 'value2', + }; + const expectedPayload = { + order_id: 'ord123', + total: 52.0, + subtotal: 45.0, + value: 50.0, + items: [{ item_code: 'prd123', brand: 'brand123' }], + property1: 'value1', + property2: 'value2', + }; + + const result = enrichTrackPayload(message, payload); + + expect(result).toEqual(expectedPayload); + }); + + it('should return the enriched payload for custom event', () => { + const message = { + event: 'someEvent', + properties: { + order_id: 'ord123', + property1: 'value1', + property2: 'value2', + revenue: 10, + value: 11, + products: [ + { + product_id: 'prd123', + test: 'test', + }, + ], + }, + }; + const payload = { + order_id: 'ord123', + value: 11, + }; + const expectedPayload = { + order_id: 'ord123', + property1: 'value1', + property2: 'value2', + revenue: 10, + value: 11, + products: [ + { + product_id: 'prd123', + test: 'test', + }, + ], + }; + + const result = enrichTrackPayload(message, payload); + expect(result).toEqual(expectedPayload); + }); +}); diff --git a/src/v0/destinations/the_trade_desk/networkHandler.js b/src/v0/destinations/the_trade_desk/networkHandler.js index e9693e8132..30378e5ace 100644 --- a/src/v0/destinations/the_trade_desk/networkHandler.js +++ b/src/v0/destinations/the_trade_desk/networkHandler.js @@ -8,37 +8,28 @@ const { getSignatureHeader } = require('../../../cdk/v2/destinations/the_trade_d const { isHttpStatusSuccess } = require('../../util/index'); const tags = require('../../util/tags'); const { JSON_MIME_TYPE } = require('../../util/constant'); -const { - REAL_TIME_CONVERSION_ENDPOINT, -} = require('../../../cdk/v2/destinations/the_trade_desk/config'); const proxyRequest = async (request) => { const { endpoint, data, method, params, headers, config } = prepareProxyRequest(request); - let ProxyHeaders = { - ...headers, - 'Content-Type': JSON_MIME_TYPE, - }; - - // For first party data flow - if (endpoint !== REAL_TIME_CONVERSION_ENDPOINT) { - if (!config?.advertiserSecretKey) { - throw new PlatformError('Advertiser secret key is missing in destination config. Aborting'); - } - if (!process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY) { - throw new PlatformError('Data provider secret key is missing. Aborting'); - } + if (!config?.advertiserSecretKey) { + throw new PlatformError('Advertiser secret key is missing in destination config. Aborting'); + } - ProxyHeaders = { - ...ProxyHeaders, - TtdSignature: getSignatureHeader(data, config.advertiserSecretKey), - 'TtdSignature-dp': getSignatureHeader( - data, - process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY, - ), - }; + if (!process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY) { + throw new PlatformError('Data provider secret key is missing. Aborting'); } + const ProxyHeaders = { + ...headers, + 'Content-Type': JSON_MIME_TYPE, + TtdSignature: getSignatureHeader(data, config.advertiserSecretKey), + 'TtdSignature-dp': getSignatureHeader( + data, + process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY, + ), + }; + const requestOptions = { url: endpoint, data, @@ -69,7 +60,6 @@ const responseHandler = (destinationResponse) => { // Trade desk first party data api returns 200 with an error in case of "Failed to parse TDID, DAID, UID2, IDL, EUID, or failed to decrypt UID2Token or EUIDToken" // https://partner.thetradedesk.com/v3/portal/data/doc/post-data-advertiser-external // {"FailedLines":[{"ErrorCode":"MissingUserId","Message":"Invalid DAID, item #1"}]} - // For real time conversion api we don't have separate response handling, trade desk always return 400 for bad events. if ('FailedLines' in response && response.FailedLines.length > 0) { throw new AbortedError( `Request failed with status: ${status} due to ${JSON.stringify(response)}`, diff --git a/test/integrations/destinations/the_trade_desk/common.ts b/test/integrations/destinations/the_trade_desk/common.ts index 8deaf60034..d792c7faae 100644 --- a/test/integrations/destinations/the_trade_desk/common.ts +++ b/test/integrations/destinations/the_trade_desk/common.ts @@ -3,7 +3,6 @@ const destTypeInUpperCase = 'THE_TRADE_DESK'; const advertiserId = 'test-advertiser-id'; const dataProviderId = 'rudderstack'; const segmentName = 'test-segment'; -const trackerId = 'test-trackerId'; const sampleDestination = { Config: { advertiserId, @@ -11,7 +10,6 @@ const sampleDestination = { dataServer: 'apac', ttlInDays: 30, audienceId: segmentName, - trackerId, }, DestinationDefinition: { Config: { cdkV2Enabled: true } }, }; @@ -35,73 +33,12 @@ const sampleContext = { sources: sampleSource, }; -const sampleContextForConversion = { - app: { - build: '1.0.0', - name: 'RudderLabs Android SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.5', - }, - device: { - adTrackingEnabled: true, - advertisingId: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', - id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', - manufacturer: 'Google', - model: 'AOSP on IA Emulator', - name: 'generic_x86_arm', - type: 'ios', - attTrackingStatus: 3, - }, - externalId: [ - { - type: 'daid', - id: 'test-daid', - }, - ], - ip: '0.0.0.0', - page: { - referrer: 'https://docs.rudderstack.com/destinations/trade_desk', - }, - library: { - name: 'RudderLabs JavaScript SDK', - version: '1.0.5', - }, - locale: 'en-GB', - os: { - name: '', - version: '', - }, - screen: { - density: 2, - }, - 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', -}; - -const integrationObject = { - All: true, - THE_TRADE_DESK: { - policies: ['LDU'], - region: 'US-CA', - privacy_settings: [ - { - privacy_type: 'GDPR', - is_applicable: 1, - consent_string: 'ok', - }, - ], - }, -}; - export { destType, destTypeInUpperCase, advertiserId, dataProviderId, segmentName, - trackerId, sampleDestination, sampleContext, - sampleContextForConversion, - integrationObject, }; diff --git a/test/integrations/destinations/the_trade_desk/delivery/data.ts b/test/integrations/destinations/the_trade_desk/delivery/data.ts index da8f60972e..320eb6dcfe 100644 --- a/test/integrations/destinations/the_trade_desk/delivery/data.ts +++ b/test/integrations/destinations/the_trade_desk/delivery/data.ts @@ -5,7 +5,6 @@ import { dataProviderId, segmentName, sampleDestination, - trackerId, } from '../common'; beforeAll(() => { @@ -246,169 +245,4 @@ export const data = [ }, }, }, - { - name: destType, - description: 'Successful delivery of realtime conversion event to Trade Desk', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - destinationResponse: { - response: { - Message: null, - EventResponses: [], - }, - status: 200, - }, - message: 'Request Processed Successfully', - status: 200, - }, - }, - }, - }, - }, - { - name: destType, - description: - 'Error response from the Trade Desk due to invalid real time conversion event payload', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - privacy_settings: [{}], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - destinationResponse: { - response: { - Message: null, - EventResponses: [ - { - EventIndex: 0, - EventErrors: [ - { - Error: 'InvalidPrivacySetting', - ErrorMessage: 'The request has an invalid privacy setting.', - }, - ], - EventWarnings: [], - Successful: false, - }, - ], - }, - status: 400, - }, - message: - 'Request failed with status: 400 due to {"Message":null,"EventResponses":[{"EventIndex":0,"EventErrors":[{"Error":"InvalidPrivacySetting","ErrorMessage":"The request has an invalid privacy setting."}],"EventWarnings":[],"Successful":false}]}', - statTags: { - destType: destTypeInUpperCase, - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - status: 400, - }, - }, - }, - }, - }, ]; diff --git a/test/integrations/destinations/the_trade_desk/network.ts b/test/integrations/destinations/the_trade_desk/network.ts index 5908cbf8f5..ed6bdf4c7d 100644 --- a/test/integrations/destinations/the_trade_desk/network.ts +++ b/test/integrations/destinations/the_trade_desk/network.ts @@ -1,4 +1,4 @@ -import { destType, advertiserId, dataProviderId, segmentName, trackerId } from './common'; +import { destType, advertiserId, dataProviderId, segmentName } from './common'; export const networkCallsData = [ { @@ -103,107 +103,4 @@ export const networkCallsData = [ statusText: 'Ok', }, }, - { - httpReq: { - url: 'https://insight.adsrvr.org/track/realtimeconversion', - data: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - }, - ], - }, - params: { destination: destType }, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'POST', - }, - httpRes: { - data: { - Message: null, - EventResponses: [], - }, - status: 200, - statusText: 'OK', - }, - }, - { - httpReq: { - url: 'https://insight.adsrvr.org/track/realtimeconversion', - data: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - privacy_settings: [{}], - }, - ], - }, - params: { destination: destType }, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'POST', - }, - httpRes: { - data: { - Message: null, - EventResponses: [ - { - EventIndex: 0, - EventErrors: [ - { - Error: 'InvalidPrivacySetting', - ErrorMessage: 'The request has an invalid privacy setting.', - }, - ], - EventWarnings: [], - Successful: false, - }, - ], - }, - status: 400, - statusText: 'Bad Request', - }, - }, ]; diff --git a/test/integrations/destinations/the_trade_desk/router/data.ts b/test/integrations/destinations/the_trade_desk/router/data.ts index 691ec703b9..f095f561db 100644 --- a/test/integrations/destinations/the_trade_desk/router/data.ts +++ b/test/integrations/destinations/the_trade_desk/router/data.ts @@ -6,11 +6,8 @@ import { advertiserId, dataProviderId, segmentName, - trackerId, sampleDestination, sampleContext, - sampleContextForConversion, - integrationObject, } from '../common'; export const data = [ @@ -868,786 +865,6 @@ export const data = [ }, mockFns: defaultMockFns, }, - { - name: destType, - description: - 'Mapped Ecommerce events (product added, product viewed, product added to wishlist, cart viewed, checkout started, order completed)', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'track', - event: 'Product Added', - messageId: 'messageId123', - context: sampleContextForConversion, - properties: { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - category: 'Games', - name: 'Cones of Dunshire', - brand: 'Wyatt Games', - variant: 'exapansion pack', - price: 49.99, - quantity: 5, - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - key1: 'value1', - }, - integrations: integrationObject, - }, - destination: overrideDestination(sampleDestination, { - customProperties: [ - { - rudderProperty: 'properties.key1', - tradeDeskProperty: 'td1', - }, - { - rudderProperty: 'properties.key2', - tradeDeskProperty: 'td2', - }, - ], - }), - metadata: { - jobId: 1, - }, - }, - { - message: { - type: 'track', - event: 'Product Viewed', - properties: { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - category: 'Games', - name: 'Cones of Dunshire', - brand: 'Wyatt Games', - variant: 'exapansion pack', - price: 49.99, - quantity: 5, - coupon: 'PREORDER15', - currency: 'USD', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - }, - }, - destination: sampleDestination, - metadata: { - jobId: 2, - }, - }, - { - message: { - type: 'track', - event: 'Product Added to Wishlist', - properties: { - wishlist_id: '74fkdjfl0jfdkdj29j030', - wishlist_name: 'New Games', - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - category: 'Games', - name: 'Cones of Dunshire', - brand: 'Wyatt Games', - variant: 'exapansion pack', - price: 49.99, - quantity: 1, - coupon: 'PREORDER15', - position: 1, - url: 'https://www.site.com/product/path', - image_url: 'https://www.site.com/product/path.jpg', - }, - }, - destination: sampleDestination, - metadata: { - jobId: 3, - }, - }, - { - message: { - type: 'track', - event: 'Cart Viewed', - properties: { - cart_id: '6b2c6f5aecf86a4ae77358ae3', - products: [ - { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - name: 'Cones of Dunshire', - price: 49.99, - position: 5, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - product_id: '577c6f5d5cf86a4c7735ba03', - sku: '3309-483-2201', - name: 'Five Crowns', - price: 5.99, - position: 2, - category: 'Games', - }, - ], - }, - }, - destination: sampleDestination, - metadata: { - jobId: 4, - }, - }, - { - message: { - type: 'track', - event: 'Checkout Started', - properties: { - order_id: '40684e8f0eaf000000000000', - affiliation: 'Vandelay Games', - value: 52, - revenue: 50.0, - shipping: 4, - tax: 3, - discount: 5, - coupon: 'NEWCUST5', - currency: 'USD', - products: [ - { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - name: 'Cones of Dunshire', - price: 40, - position: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - product_id: '577c6f5d5cf86a4c7735ba03', - sku: '3309-483-2201', - name: 'Five Crowns', - price: 5, - position: 2, - category: 'Games', - }, - ], - }, - }, - destination: sampleDestination, - metadata: { - jobId: 5, - }, - }, - { - message: { - type: 'track', - event: 'Order Completed', - properties: { - checkout_id: '70324a1f0eaf000000000000', - order_id: '40684e8f0eaf000000000000', - affiliation: 'Vandelay Games', - total: 52.0, - subtotal: 45.0, - revenue: 50.0, - shipping: 4.0, - tax: 3.0, - discount: 5.0, - coupon: 'NEWCUST5', - currency: 'USD', - products: [ - { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - name: 'Cones of Dunshire', - price: 40, - position: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - product_id: '577c6f5d5cf86a4c7735ba03', - sku: '3309-483-2201', - name: 'Five Crowns', - price: 5, - position: 2, - category: 'Games', - }, - ], - }, - }, - destination: sampleDestination, - metadata: { - jobId: 6, - }, - }, - ], - destType, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'addtocart', - value: 249.95000000000002, - adid: 'test-daid', - adid_type: 'DAID', - client_ip: '0.0.0.0', - referrer_url: 'https://docs.rudderstack.com/destinations/trade_desk', - imp: 'messageId123', - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - td1: 'value1', - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - data_processing_option: { - policies: ['LDU'], - region: 'US-CA', - }, - privacy_settings: [ - { - privacy_type: 'GDPR', - is_applicable: 1, - consent_string: 'ok', - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 1, - }, - ], - batched: false, - statusCode: 200, - destination: overrideDestination(sampleDestination, { - customProperties: [ - { - rudderProperty: 'properties.key1', - tradeDeskProperty: 'td1', - }, - { - rudderProperty: 'properties.key2', - tradeDeskProperty: 'td2', - }, - ], - }), - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 2, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'wishlistitem', - value: 49.99, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 1, - price: 49.99, - }, - ], - wishlist_id: '74fkdjfl0jfdkdj29j030', - wishlist_name: 'New Games', - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.site.com/product/path', - image_url: 'https://www.site.com/product/path.jpg', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 3, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'viewcart', - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - price: 49.99, - position: 5, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - item_code: '577c6f5d5cf86a4c7735ba03', - name: 'Five Crowns', - price: 5.99, - position: 2, - category: 'Games', - }, - ], - cart_id: '6b2c6f5aecf86a4ae77358ae3', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 4, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - order_id: '40684e8f0eaf000000000000', - event_name: 'startcheckout', - value: 50, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - price: 40, - position: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - item_code: '577c6f5d5cf86a4c7735ba03', - name: 'Five Crowns', - price: 5, - position: 2, - category: 'Games', - }, - ], - affiliation: 'Vandelay Games', - shipping: 4, - tax: 3, - discount: 5, - coupon: 'NEWCUST5', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - order_id: '40684e8f0eaf000000000000', - event_name: 'purchase', - value: 50, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - price: 40, - position: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - item_code: '577c6f5d5cf86a4c7735ba03', - name: 'Five Crowns', - price: 5, - position: 2, - category: 'Games', - }, - ], - checkout_id: '70324a1f0eaf000000000000', - affiliation: 'Vandelay Games', - total: 52.0, - subtotal: 45.0, - shipping: 4.0, - tax: 3.0, - discount: 5.0, - coupon: 'NEWCUST5', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 6, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - ], - }, - }, - }, - }, - { - name: destType, - description: 'Custom event', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'track', - event: 'custom event abc', - properties: { - key1: 'value1', - value: 25, - product_id: 'prd123', - key2: true, - test: 'test123', - }, - }, - destination: overrideDestination(sampleDestination, { - customProperties: [ - { - rudderProperty: 'properties.key1', - tradeDeskProperty: 'td1', - }, - { - rudderProperty: 'properties.key2', - tradeDeskProperty: 'td2', - }, - ], - }), - metadata: { - jobId: 1, - }, - }, - ], - destType, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'custom event abc', - value: 25, - product_id: 'prd123', - test: 'test123', - td1: 'value1', - td2: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 1, - }, - ], - batched: false, - statusCode: 200, - destination: overrideDestination(sampleDestination, { - customProperties: [ - { - rudderProperty: 'properties.key1', - tradeDeskProperty: 'td1', - }, - { - rudderProperty: 'properties.key2', - tradeDeskProperty: 'td2', - }, - ], - }), - }, - ], - }, - }, - }, - }, - { - name: destType, - description: 'Mapped standard trade desk event', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'track', - event: 'custom event abc', - properties: { - key1: 'value1', - value: 25, - product_id: 'prd123', - key2: true, - test: 'test123', - }, - }, - destination: overrideDestination(sampleDestination, { - eventsMapping: [ - { - from: 'custom event abc', - to: 'direction', - }, - ], - }), - metadata: { - jobId: 1, - }, - }, - ], - destType, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'direction', - value: 25, - product_id: 'prd123', - test: 'test123', - key1: 'value1', - key2: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 1, - }, - ], - batched: false, - statusCode: 200, - destination: overrideDestination(sampleDestination, { - eventsMapping: [ - { - from: 'custom event abc', - to: 'direction', - }, - ], - }), - }, - ], - }, - }, - }, - }, { name: destType, description: 'Batch call with different event types', @@ -1674,30 +891,6 @@ export const data = [ jobId: 1, }, }, - { - message: { - type: 'track', - event: 'custom event abc', - properties: { - key1: 'value1', - value: 25, - revenue: 10, - product_id: 'prd123', - key2: true, - test: 'test123', - products: [ - { - product_id: 'prd123', - test: 'test', - }, - ], - }, - }, - destination: sampleDestination, - metadata: { - jobId: 2, - }, - }, { message: { type: 'identify', @@ -1711,7 +904,7 @@ export const data = [ }, destination: sampleDestination, metadata: { - jobId: 3, + jobId: 2, }, }, ], @@ -1767,53 +960,8 @@ export const data = [ destination: sampleDestination, }, { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'custom event abc', - value: 25, - product_id: 'prd123', - test: 'test123', - key1: 'value1', - key2: true, - revenue: 10, - products: [ - { - product_id: 'prd123', - test: 'test', - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 2, - }, - ], batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batched: false, - metadata: [{ jobId: 3 }], + metadata: [{ jobId: 2 }], statusCode: 400, error: 'Event type identify is not supported', statTags: { @@ -1831,119 +979,4 @@ export const data = [ }, }, }, - { - name: destType, - description: 'Tracker id is not present', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: overrideDestination(sampleDestination, { trackerId: '' }), - metadata: { - jobId: 1, - }, - }, - { - message: { - type: 'track', - event: 'custom event abc', - properties: { - key1: 'value1', - value: 25, - product_id: 'prd123', - key2: true, - test: 'test123', - }, - }, - destination: overrideDestination(sampleDestination, { trackerId: '' }), - metadata: { - jobId: 2, - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: [ - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - ], - metadata: [ - { - jobId: 1, - }, - ], - batched: true, - statusCode: 200, - destination: overrideDestination(sampleDestination, { trackerId: '' }), - }, - { - batched: false, - metadata: [{ jobId: 2 }], - statusCode: 400, - error: 'Tracking Tag ID is not present. Aborting', - statTags: { - errorCategory: 'dataValidation', - errorType: 'configuration', - destType: 'THE_TRADE_DESK', - module: 'destination', - implementation: 'cdkV2', - feature: 'router', - }, - destination: overrideDestination(sampleDestination, { trackerId: '' }), - }, - ], - }, - }, - }, - }, ]; diff --git a/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts b/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts new file mode 100644 index 0000000000..3af7791ec8 --- /dev/null +++ b/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts @@ -0,0 +1,79 @@ +const destType = 'the_trade_desk_real_time_conversions'; +const destTypeInUpperCase = 'THE_TRADE_DESK_REAL_TIME_CONVERSIONS'; +const advertiserId = 'test-advertiser-id'; +const trackerId = 'test-trackerId'; +const sampleDestination = { + Config: { + advertiserId, + trackerId, + }, + DestinationDefinition: { Config: { cdkV2Enabled: true } }, +}; + +const sampleContextForConversion = { + app: { + build: '1.0.0', + name: 'RudderLabs Android SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.5', + }, + device: { + adTrackingEnabled: true, + advertisingId: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + manufacturer: 'Google', + model: 'AOSP on IA Emulator', + name: 'generic_x86_arm', + type: 'ios', + attTrackingStatus: 3, + }, + externalId: [ + { + type: 'daid', + id: 'test-daid', + }, + ], + ip: '0.0.0.0', + page: { + referrer: 'https://docs.rudderstack.com/destinations/trade_desk', + }, + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.5', + }, + locale: 'en-GB', + os: { + name: '', + version: '', + }, + screen: { + density: 2, + }, + 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', +}; + +const integrationObject = { + All: true, + THE_TRADE_DESK: { + policies: ['LDU'], + region: 'US-CA', + privacy_settings: [ + { + privacy_type: 'GDPR', + is_applicable: 1, + consent_string: 'ok', + }, + ], + }, +}; + +export { + destType, + destTypeInUpperCase, + advertiserId, + trackerId, + sampleDestination, + sampleContextForConversion, + integrationObject, +}; diff --git a/test/integrations/destinations/the_trade_desk_real_time_conversions/processor/data.ts b/test/integrations/destinations/the_trade_desk_real_time_conversions/processor/data.ts new file mode 100644 index 0000000000..264c760088 --- /dev/null +++ b/test/integrations/destinations/the_trade_desk_real_time_conversions/processor/data.ts @@ -0,0 +1,984 @@ +import { overrideDestination } from '../../../testUtils'; +import { + destType, + destTypeInUpperCase, + advertiserId, + trackerId, + sampleDestination, + sampleContextForConversion, + integrationObject, +} from '../common'; + +export const data = [ + { + name: destType, + description: 'Missing advertiser ID in the config', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'custom event abc', + properties: { + key1: 'value1', + value: 25, + product_id: 'prd123', + key2: true, + test: 'test123', + }, + }, + destination: overrideDestination(sampleDestination, { advertiserId: '' }), + metadata: { + jobId: 1, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Advertiser ID is not present. Aborting: Workflow: procWorkflow, Step: validateConfig, ChildStep: undefined, OriginalError: Advertiser ID is not present. Aborting', + statTags: { + destType: destTypeInUpperCase, + implementation: 'cdkV2', + feature: 'processor', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'configuration', + }, + metadata: { + jobId: 1, + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Tracker id is not present', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'custom event abc', + properties: { + key1: 'value1', + value: 25, + product_id: 'prd123', + key2: true, + test: 'test123', + }, + }, + destination: overrideDestination(sampleDestination, { trackerId: '' }), + metadata: { + jobId: 1, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Tracking Tag ID is not present. Aborting: Workflow: procWorkflow, Step: validateConfig, ChildStep: undefined, OriginalError: Tracking Tag ID is not present. Aborting', + statTags: { + destType: destTypeInUpperCase, + implementation: 'cdkV2', + feature: 'processor', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'configuration', + }, + metadata: { + jobId: 1, + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Unsupported event type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'identify', + context: { + traits: { + name: 'John Doe', + email: 'johndoe@gmail.com', + age: 25, + }, + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Event type identify is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: Event type identify is not supported', + statTags: { + destType: destTypeInUpperCase, + implementation: 'cdkV2', + feature: 'processor', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + }, + metadata: { + jobId: 1, + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Product Added', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Product Added', + messageId: 'messageId123', + context: sampleContextForConversion, + properties: { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + category: 'Games', + name: 'Cones of Dunshire', + brand: 'Wyatt Games', + variant: 'expansion pack', + price: 49.99, + quantity: 5, + coupon: 'PREORDER15', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + key1: 'value1', + }, + integrations: integrationObject, + }, + destination: overrideDestination(sampleDestination, { + customProperties: [ + { + rudderProperty: 'properties.key1', + tradeDeskProperty: 'td1', + }, + { + rudderProperty: 'properties.key2', + tradeDeskProperty: 'td2', + }, + ], + }), + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'addtocart', + value: 249.95000000000002, + adid: 'test-daid', + adid_type: 'DAID', + client_ip: '0.0.0.0', + referrer_url: 'https://docs.rudderstack.com/destinations/trade_desk', + imp: 'messageId123', + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + qty: 5, + price: 49.99, + }, + ], + td1: 'value1', + category: 'Games', + brand: 'Wyatt Games', + variant: 'expansion pack', + coupon: 'PREORDER15', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + data_processing_option: { + policies: ['LDU'], + region: 'US-CA', + }, + privacy_settings: [ + { + privacy_type: 'GDPR', + is_applicable: 1, + consent_string: 'ok', + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Product Viewed', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Product Viewed', + properties: { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + category: 'Games', + name: 'Cones of Dunshire', + brand: 'Wyatt Games', + variant: 'expansion pack', + price: 49.99, + quantity: 5, + coupon: 'PREORDER15', + currency: 'USD', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + currency: 'USD', + event_name: 'viewitem', + value: 249.95000000000002, + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + qty: 5, + price: 49.99, + }, + ], + category: 'Games', + brand: 'Wyatt Games', + variant: 'expansion pack', + coupon: 'PREORDER15', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Product Added to Wishlist', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Product Added to Wishlist', + properties: { + wishlist_id: '74fkdjfl0jfdkdj29j030', + wishlist_name: 'New Games', + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + category: 'Games', + name: 'Cones of Dunshire', + brand: 'Wyatt Games', + variant: 'expansion pack', + price: 49.99, + quantity: 1, + coupon: 'PREORDER15', + position: 1, + url: 'https://www.site.com/product/path', + image_url: 'https://www.site.com/product/path.jpg', + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'wishlistitem', + value: 49.99, + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + qty: 1, + price: 49.99, + }, + ], + wishlist_id: '74fkdjfl0jfdkdj29j030', + wishlist_name: 'New Games', + category: 'Games', + brand: 'Wyatt Games', + variant: 'expansion pack', + coupon: 'PREORDER15', + position: 1, + url: 'https://www.site.com/product/path', + image_url: 'https://www.site.com/product/path.jpg', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Cart Viewed', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Cart Viewed', + properties: { + cart_id: '6b2c6f5aecf86a4ae77358ae3', + products: [ + { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + name: 'Cones of Dunshire', + price: 49.99, + position: 5, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '577c6f5d5cf86a4c7735ba03', + sku: '3309-483-2201', + name: 'Five Crowns', + price: 5.99, + position: 2, + category: 'Games', + }, + ], + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'viewcart', + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + price: 49.99, + position: 5, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + item_code: '577c6f5d5cf86a4c7735ba03', + name: 'Five Crowns', + price: 5.99, + position: 2, + category: 'Games', + }, + ], + cart_id: '6b2c6f5aecf86a4ae77358ae3', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Checkout Started', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Checkout Started', + properties: { + order_id: '40684e8f0eaf000000000000', + affiliation: 'Vandelay Games', + value: 52, + revenue: 50.0, + shipping: 4, + tax: 3, + discount: 5, + coupon: 'NEWCUST5', + currency: 'USD', + products: [ + { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + name: 'Cones of Dunshire', + price: 40, + position: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '577c6f5d5cf86a4c7735ba03', + sku: '3309-483-2201', + name: 'Five Crowns', + price: 5, + position: 2, + category: 'Games', + }, + ], + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + currency: 'USD', + order_id: '40684e8f0eaf000000000000', + event_name: 'startcheckout', + value: 50, + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + price: 40, + position: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + item_code: '577c6f5d5cf86a4c7735ba03', + name: 'Five Crowns', + price: 5, + position: 2, + category: 'Games', + }, + ], + affiliation: 'Vandelay Games', + shipping: 4, + tax: 3, + discount: 5, + coupon: 'NEWCUST5', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Order Completed', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Order Completed', + properties: { + checkout_id: '70324a1f0eaf000000000000', + order_id: '40684e8f0eaf000000000000', + affiliation: 'Vandelay Games', + total: 52.0, + subtotal: 45.0, + revenue: 50.0, + shipping: 4.0, + tax: 3.0, + discount: 5.0, + coupon: 'NEWCUST5', + currency: 'USD', + products: [ + { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + name: 'Cones of Dunshire', + price: 40, + position: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '577c6f5d5cf86a4c7735ba03', + sku: '3309-483-2201', + name: 'Five Crowns', + price: 5, + position: 2, + category: 'Games', + }, + ], + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + currency: 'USD', + order_id: '40684e8f0eaf000000000000', + event_name: 'purchase', + value: 50, + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + price: 40, + position: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + item_code: '577c6f5d5cf86a4c7735ba03', + name: 'Five Crowns', + price: 5, + position: 2, + category: 'Games', + }, + ], + checkout_id: '70324a1f0eaf000000000000', + affiliation: 'Vandelay Games', + total: 52.0, + subtotal: 45.0, + shipping: 4.0, + tax: 3.0, + discount: 5.0, + coupon: 'NEWCUST5', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Custom event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'custom event abc', + properties: { + key1: 'value1', + value: 25, + product_id: 'prd123', + key2: true, + test: 'test123', + }, + }, + destination: overrideDestination(sampleDestination, { + customProperties: [ + { + rudderProperty: 'properties.key1', + tradeDeskProperty: 'td1', + }, + { + rudderProperty: 'properties.key2', + tradeDeskProperty: 'td2', + }, + ], + }), + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'custom event abc', + value: 25, + product_id: 'prd123', + test: 'test123', + td1: 'value1', + td2: true, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Mapped standard trade desk event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'custom event abc', + properties: { + key1: 'value1', + value: 25, + product_id: 'prd123', + key2: true, + test: 'test123', + }, + }, + destination: overrideDestination(sampleDestination, { + eventsMapping: [ + { + from: 'custom event abc', + to: 'direction', + }, + ], + }), + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'direction', + value: 25, + product_id: 'prd123', + test: 'test123', + key1: 'value1', + key2: true, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, +]; From aa4b8b471a647544a3e34dad335f24bec9d9d06b Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:25:35 +0530 Subject: [PATCH 14/33] feat: add custom property mapping feature for freshsales identify call (#3065) * feat: initial commit * feat: adding custom field support * fix: code review addressed * fix: small edit * fix: addressed review comments --- src/v0/destinations/freshsales/transform.js | 18 ++- src/v0/destinations/freshsales/utils.js | 31 ++++ src/v0/destinations/freshsales/utils.test.js | 91 ++++++++++++ src/v0/util/index.js | 55 ++++++- src/v0/util/index.test.js | 1 + .../generateExclusionListUsingKeyPaths.json | 37 +++++ .../destinations/freshsales/processor/data.ts | 135 ++++++++++++++++++ .../destinations/freshsales/router/data.ts | 1 + 8 files changed, 365 insertions(+), 4 deletions(-) create mode 100644 src/v0/destinations/freshsales/utils.test.js create mode 100644 src/v0/util/testdata/generateExclusionListUsingKeyPaths.json diff --git a/src/v0/destinations/freshsales/transform.js b/src/v0/destinations/freshsales/transform.js index 096a2d749c..8fde9c003c 100644 --- a/src/v0/destinations/freshsales/transform.js +++ b/src/v0/destinations/freshsales/transform.js @@ -19,6 +19,7 @@ const { UpdateContactWithLifeCycleStage, updateAccountWOContact, getHeaders, + populatePayloadWithCustomFields, } = require('./utils'); /* @@ -41,6 +42,7 @@ const identifyResponseConfig = (Config) => { * @returns */ const identifyResponseBuilder = (message, { Config }) => { + const { customPropertyMapping, apiKey, domain } = Config; const payload = constructPayload(message, MAPPING_CONFIG[CONFIG_CATEGORIES.IDENTIFY.name]); if (!payload) { @@ -49,12 +51,22 @@ const identifyResponseBuilder = (message, { Config }) => { } if (payload.address) payload.address = flattenAddress(payload.address); + + // adding support for custom properties + const updatedPayload = { + ...populatePayloadWithCustomFields( + message, + customPropertyMapping, + payload, + MAPPING_CONFIG[CONFIG_CATEGORIES.IDENTIFY.name], + ), + }; const response = defaultRequestConfig(); - response.headers = getHeaders(Config.apiKey); - response.endpoint = `https://${Config.domain}${CONFIG_CATEGORIES.IDENTIFY.baseUrl}`; + response.headers = getHeaders(apiKey); + response.endpoint = `https://${domain}${CONFIG_CATEGORIES.IDENTIFY.baseUrl}`; response.method = CONFIG_CATEGORIES.IDENTIFY.method; response.body.JSON = { - contact: payload, + contact: updatedPayload, unique_identifier: { emails: payload.emails }, }; return response; diff --git a/src/v0/destinations/freshsales/utils.js b/src/v0/destinations/freshsales/utils.js index 5008fedc2d..5b781b1af6 100644 --- a/src/v0/destinations/freshsales/utils.js +++ b/src/v0/destinations/freshsales/utils.js @@ -4,6 +4,7 @@ const { NetworkInstrumentationError, InstrumentationError, NetworkError, + getHashFromArray, } = require('@rudderstack/integrations-lib'); const { httpPOST, httpGET } = require('../../../adapters/network'); const { @@ -14,6 +15,8 @@ const { defaultRequestConfig, defaultPostRequestConfig, getFieldValueFromMessage, + extractCustomFields, + generateExclusionListUsingKeyPaths, } = require('../../util'); const { CONFIG_CATEGORIES, LIFECYCLE_STAGE_ENDPOINT } = require('./config'); const tags = require('../../util/tags'); @@ -384,6 +387,33 @@ const flattenAddress = (address) => { return result; }; +const populatePayloadWithCustomFields = ( + message, + customPropertyMapping, + payload, + MAPPING_CONFIG, +) => { + const rawPayload = { ...payload }; + const traits = getFieldValueFromMessage(message, 'traits'); + const itemExclusionList = generateExclusionListUsingKeyPaths(MAPPING_CONFIG); + const customField = {}; + if (customPropertyMapping && customPropertyMapping.length > 0) { + const propertyMap = getHashFromArray(customPropertyMapping, 'from', 'to', false); + Object.keys(traits).forEach((key) => { + if (propertyMap[key]) { + itemExclusionList.push(key); + customField[propertyMap[key]] = traits[key]; + } + }); + } + // adding all other trait fields as custom fields, freshsales will handle any non configured fields by itself and reject the values + rawPayload.custom_field = { + ...customField, + ...extractCustomFields(message, {}, ['traits', 'context.traits'], itemExclusionList), + }; + return rawPayload; +}; + module.exports = { getUserAccountDetails, flattenAddress, @@ -391,4 +421,5 @@ module.exports = { UpdateContactWithLifeCycleStage, updateAccountWOContact, getHeaders, + populatePayloadWithCustomFields, }; diff --git a/src/v0/destinations/freshsales/utils.test.js b/src/v0/destinations/freshsales/utils.test.js new file mode 100644 index 0000000000..5f6bd761a9 --- /dev/null +++ b/src/v0/destinations/freshsales/utils.test.js @@ -0,0 +1,91 @@ +const { populatePayloadWithCustomFields } = require('./utils'); + +describe('populatePayloadWithCustomFields', () => { + it('Mapping config is empty', () => { + const message = { + traits: { + email: 'test@example.com', + firstName: 'John', + lastName: 'Doe', + newProp: 'newPropValue', + }, + }; + const customPropertyMapping = [{ from: 'newProp', to: 'cf_newProp' }]; + const payload = {}; + const MAPPING_CONFIG = []; + + const result = populatePayloadWithCustomFields( + message, + customPropertyMapping, + payload, + MAPPING_CONFIG, + ); + + expect(result).toEqual({ + custom_field: { + email: 'test@example.com', + firstName: 'John', + lastName: 'Doe', + cf_newProp: 'newPropValue', + }, + }); + }); + + it('should exclude specified fields from being added as custom fields', () => { + const message = { + traits: { + email: 'test@example.com', + first_name: 'John', + lastName: 'Doe', + newProp: 'newPropValue', + }, + }; + const customPropertyMapping = [{ from: 'newProp', to: 'cf_newProp' }]; + const payload = {}; + const MAPPING_CONFIG = [ + { destKey: 'first_name', sourceKeys: 'firstName', sourceFromGenericMap: true }, + { destKey: 'email', sourceKeys: 'email', sourceFromGenericMap: true }, + ]; + + const result = populatePayloadWithCustomFields( + message, + customPropertyMapping, + payload, + MAPPING_CONFIG, + ); + + expect(result).toEqual({ + custom_field: { + cf_newProp: 'newPropValue', + lastName: 'Doe', + }, + }); + }); + + it('should not overwrite existing payload data', () => { + const message = { + traits: { + firstName: 'John', + }, + }; + const customPropertyMapping = [{ from: 'firstName', to: 'first_name' }]; + const initialPayload = { + existingField: 'existingValue', + }; + const MAPPING_CONFIG = []; + + const result = populatePayloadWithCustomFields( + message, + customPropertyMapping, + initialPayload, + MAPPING_CONFIG, + ); + + expect(result).toEqual({ + existingField: 'existingValue', + custom_field: { + first_name: 'John', + }, + }); + }); +}); diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 0cc66b2d7a..fd2eb1fb61 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -34,6 +34,7 @@ const { } = require('../../adapters/networkhandler/authConstants'); const { FEATURE_FILTER_CODE, FEATURE_GZIP_SUPPORT } = require('./constant'); const { CommonUtils } = require('../../util/common'); +const genericFieldMapping = require('./data/GenericFieldMapping.json'); // ======================================================================== // INLINERS @@ -1305,6 +1306,55 @@ const generateExclusionList = (mappingConfig) => Array.isArray(mapping.sourceKeys) ? [...mapping.sourceKeys] : [mapping.sourceKeys], ); +/** + * Generates an exclusion list using key paths based on the provided mapping configuration. + * + * @param {Array} mappingConfig - The traditional mapping configuration used build payloads. + * example : + * [ + * { + * "destKey": "email", + * "sourceKeys": "email", + * "required": true, + * "sourceFromGenericMap": true + * }, + * { + * "destKey": "key", + * "sourceKeys": ["context.traits.key", "traits.key"], + * } + * ] + * @returns {Array} - The generated exclusion list, which can be fed in the "extractCustomFields" function. + */ +const generateExclusionListUsingKeyPaths = (mappingConfig) => { + const resultList = mappingConfig.flatMap((mapping) => { + if (mapping.sourceFromGenericMap) { + // If sourceFromGenericMap is true, use the mapping for generic fields + const genericMappings = genericFieldMapping[mapping.sourceKeys]; + if (isDefinedAndNotNull(genericMappings)) { + if (Array.isArray(genericMappings)) { + // Map over the genericMappings to extract the last part of each path + return genericMappings.map((paths) => paths.split('.').pop()); + } + // Handle the case where it's a single string, not an array + return [genericMappings.split('.').pop()]; + } + } + + // Handle the case where sourceFromGenericMap is not true or does not exist + // This also assumes that the non-generic mappings might be in dot notation and extracts the last key + if (isDefinedAndNotNull(mapping.sourceKeys)) { + return Array.isArray(mapping.sourceKeys) + ? mapping.sourceKeys.map((key) => key.split('.').pop()) + : [mapping.sourceKeys.split('.').pop()]; + } + throw new TransformationError('sourceKeys is not defined in the mapping configuration'); + }); + // Use a Set to filter out duplicates, then convert it back to an array + const uniqueResultList = [...new Set(resultList)]; + + return uniqueResultList; +}; + /** * Extract fileds from message with exclusions * Pass the keys of message for extraction and @@ -1341,7 +1391,9 @@ function extractCustomFields(message, payload, keys, exclusionFields) { const messageContext = get(message, key); if (messageContext) { Object.keys(messageContext).forEach((k) => { - if (!exclusionFields.includes(k)) mappingKeys.push(k); + if (!exclusionFields.includes(k)) { + mappingKeys.push(k); + } }); mappingKeys.forEach((mappingKey) => { if (!(typeof messageContext[mappingKey] === 'undefined')) { @@ -2334,4 +2386,5 @@ module.exports = { findExistingBatch, removeDuplicateMetadata, combineBatchRequestsWithSameJobIds, + generateExclusionListUsingKeyPaths, }; diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js index 4dc6255691..f0e2845d57 100644 --- a/src/v0/util/index.test.js +++ b/src/v0/util/index.test.js @@ -21,6 +21,7 @@ const functionNames = [ 'removeUndefinedNullValuesAndEmptyObjectArray', 'groupEventsByType', 'isValidInteger', + 'generateExclusionListUsingKeyPaths', ]; // Names of the utility functions to test which expects multiple arguments as values and not objects diff --git a/src/v0/util/testdata/generateExclusionListUsingKeyPaths.json b/src/v0/util/testdata/generateExclusionListUsingKeyPaths.json new file mode 100644 index 0000000000..c1cf1c3197 --- /dev/null +++ b/src/v0/util/testdata/generateExclusionListUsingKeyPaths.json @@ -0,0 +1,37 @@ +[ + { + "description": "should correctly generate exclusion list for a simple mapping configuration", + "input": [ + [ + { + "destKey": "email", + "sourceKeys": "email", + "required": true, + "sourceFromGenericMap": true + }, + { + "destKey": "key", + "sourceKeys": "context.traits.key" + } + ] + ], + "output": ["email", "id", "key"] + }, + { + "description": "should correctly generate exclusion list for a simple mapping configuration", + "input": [ + [ + { + "destKey": "email", + "sourceKeys": ["context.email", "traits.email"], + "required": true + }, + { + "destKey": "key", + "sourceKeys": ["context.traits.key", "traits.key"] + } + ] + ], + "output": ["email", "key"] + } +] diff --git a/test/integrations/destinations/freshsales/processor/data.ts b/test/integrations/destinations/freshsales/processor/data.ts index eca3b88d9d..b16c3d18c7 100644 --- a/test/integrations/destinations/freshsales/processor/data.ts +++ b/test/integrations/destinations/freshsales/processor/data.ts @@ -189,6 +189,9 @@ export const data = [ work_number: '9988776655', mobile_number: '1-926-555-9504', lifecycle_stage_id: 71010794467, + custom_field: { + owner_id: '70000090119', + }, }, unique_identifier: { emails: 'testuser@google.com', @@ -300,6 +303,9 @@ export const data = [ work_number: '9988776655', mobile_number: '1-926-555-9504', lifecycle_stage_id: 71010794467, + custom_field: { + owner_id: '70000090119', + }, }, unique_identifier: { emails: 'testuser@google.com', @@ -418,6 +424,7 @@ export const data = [ mobile_number: '1-926-555-9504', created_at: '2022-06-22T10:57:58Z', updated_at: '2022-06-22T10:57:58Z', + custom_field: {}, }, unique_identifier: { emails: 'testuser@google.com', @@ -2685,4 +2692,132 @@ export const data = [ }, }, }, + { + name: 'freshsales', + description: 'Identify call for creating new user along with custom property mapping', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + apiKey: 'dummyApiKey', + domain: 'rudderstack-476952domain3105.myfreshworks.com', + customPropertyMapping: [ + { + from: 'newProp1', + to: 'cf_newProp1', + }, + { + from: 'newProp2', + to: 'cf_newProp2', + }, + ], + }, + }, + message: { + messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + originalTimestamp: '2022-06-22T10:57:58Z', + anonymousId: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + device: { + advertisingId: 'T0T0T072-5e28-45a1-9eda-ce22a3e36d1a', + id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + manufacturer: 'Google', + model: 'AOSP on IA Emulator', + name: 'generic_x86_arm', + type: 'ios', + attTrackingStatus: 3, + }, + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + locale: 'en-US', + os: { + name: 'iOS', + version: '14.4.1', + }, + screen: { + density: 2, + }, + }, + traits: { + email: 'testuser@google.com', + first_name: 'Rk', + last_name: 'Mishra', + mobileNumber: '1-926-555-9504', + lifecycleStageId: 71010794467, + phone: '9988776655', + owner_id: '70000090119', + newProp1: 'value1', + newProp2: 'value2', + }, + type: 'identify', + sentAt: '2022-04-22T10:57:58Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + XML: {}, + FORM: {}, + JSON: { + contact: { + emails: 'testuser@google.com', + first_name: 'Rk', + last_name: 'Mishra', + work_number: '9988776655', + external_id: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', + mobile_number: '1-926-555-9504', + created_at: '2022-06-22T10:57:58Z', + updated_at: '2022-06-22T10:57:58Z', + lifecycle_stage_id: 71010794467, + custom_field: { + cf_newProp1: 'value1', + cf_newProp2: 'value2', + owner_id: '70000090119', + }, + }, + unique_identifier: { + emails: 'testuser@google.com', + }, + }, + JSON_ARRAY: {}, + }, + type: 'REST', + files: {}, + method: 'POST', + params: {}, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Token token=dummyApiKey', + }, + version: '1', + endpoint: + 'https://rudderstack-476952domain3105.myfreshworks.com/crm/sales/api/contacts/upsert', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/freshsales/router/data.ts b/test/integrations/destinations/freshsales/router/data.ts index 8548d337b3..8d76a39570 100644 --- a/test/integrations/destinations/freshsales/router/data.ts +++ b/test/integrations/destinations/freshsales/router/data.ts @@ -115,6 +115,7 @@ export const data = [ zipcode: 'postalCode', created_at: '2022-08-30T11:28:43.647+05:30', updated_at: '2022-08-30T11:28:43.647+05:30', + custom_field: {}, }, unique_identifier: { emails: 'user112@mail.com' }, }, From 1b85e1c6f7ce80ac5acd20a8ceb0f72ea0a418f0 Mon Sep 17 00:00:00 2001 From: Sankeerth Date: Mon, 19 Feb 2024 11:44:33 +0530 Subject: [PATCH 15/33] chore: add member to hotfix branch creation workflow (#3101) Signed-off-by: Sai Sankeerth Co-authored-by: Sai Sankeerth --- .github/workflows/create-hotfix-branch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-hotfix-branch.yml b/.github/workflows/create-hotfix-branch.yml index a164c25bee..ec89f8d342 100644 --- a/.github/workflows/create-hotfix-branch.yml +++ b/.github/workflows/create-hotfix-branch.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest # Only allow these users to create new hotfix branch from 'main' - if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'koladilip' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'ujjwal-ab') + if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'koladilip' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'sanpj2292') steps: - name: Create Branch uses: peterjgrainger/action-create-branch@v2.4.0 From bfd2ac680070b8053f5674dc04e4c78692634ba2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:37:56 +0530 Subject: [PATCH 16/33] chore(deps): bump slackapi/slack-github-action from 1.24.0 to 1.25.0 (#3035) Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 1.24.0 to 1.25.0. - [Release notes](https://github.com/slackapi/slack-github-action/releases) - [Commits](https://github.com/slackapi/slack-github-action/compare/v1.24.0...v1.25.0) --- updated-dependencies: - dependency-name: slackapi/slack-github-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-new-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index 9d1558d826..305e27673e 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -89,7 +89,7 @@ jobs: - name: Notify Slack Channel id: slack - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 continue-on-error: true env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} From 20c565c87cc69afed8646609d5d96b8b7d303d7a Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:39:42 +0530 Subject: [PATCH 17/33] Revert "feat: add custom property mapping feature for freshsales identify call" (#3102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "feat: add custom property mapping feature for freshsales identify cal…" This reverts commit aa4b8b471a647544a3e34dad335f24bec9d9d06b. --- src/v0/destinations/freshsales/transform.js | 18 +-- src/v0/destinations/freshsales/utils.js | 31 ---- src/v0/destinations/freshsales/utils.test.js | 91 ------------ src/v0/util/index.js | 55 +------ src/v0/util/index.test.js | 1 - .../generateExclusionListUsingKeyPaths.json | 37 ----- .../destinations/freshsales/processor/data.ts | 135 ------------------ .../destinations/freshsales/router/data.ts | 1 - 8 files changed, 4 insertions(+), 365 deletions(-) delete mode 100644 src/v0/destinations/freshsales/utils.test.js delete mode 100644 src/v0/util/testdata/generateExclusionListUsingKeyPaths.json diff --git a/src/v0/destinations/freshsales/transform.js b/src/v0/destinations/freshsales/transform.js index 8fde9c003c..096a2d749c 100644 --- a/src/v0/destinations/freshsales/transform.js +++ b/src/v0/destinations/freshsales/transform.js @@ -19,7 +19,6 @@ const { UpdateContactWithLifeCycleStage, updateAccountWOContact, getHeaders, - populatePayloadWithCustomFields, } = require('./utils'); /* @@ -42,7 +41,6 @@ const identifyResponseConfig = (Config) => { * @returns */ const identifyResponseBuilder = (message, { Config }) => { - const { customPropertyMapping, apiKey, domain } = Config; const payload = constructPayload(message, MAPPING_CONFIG[CONFIG_CATEGORIES.IDENTIFY.name]); if (!payload) { @@ -51,22 +49,12 @@ const identifyResponseBuilder = (message, { Config }) => { } if (payload.address) payload.address = flattenAddress(payload.address); - - // adding support for custom properties - const updatedPayload = { - ...populatePayloadWithCustomFields( - message, - customPropertyMapping, - payload, - MAPPING_CONFIG[CONFIG_CATEGORIES.IDENTIFY.name], - ), - }; const response = defaultRequestConfig(); - response.headers = getHeaders(apiKey); - response.endpoint = `https://${domain}${CONFIG_CATEGORIES.IDENTIFY.baseUrl}`; + response.headers = getHeaders(Config.apiKey); + response.endpoint = `https://${Config.domain}${CONFIG_CATEGORIES.IDENTIFY.baseUrl}`; response.method = CONFIG_CATEGORIES.IDENTIFY.method; response.body.JSON = { - contact: updatedPayload, + contact: payload, unique_identifier: { emails: payload.emails }, }; return response; diff --git a/src/v0/destinations/freshsales/utils.js b/src/v0/destinations/freshsales/utils.js index 5b781b1af6..5008fedc2d 100644 --- a/src/v0/destinations/freshsales/utils.js +++ b/src/v0/destinations/freshsales/utils.js @@ -4,7 +4,6 @@ const { NetworkInstrumentationError, InstrumentationError, NetworkError, - getHashFromArray, } = require('@rudderstack/integrations-lib'); const { httpPOST, httpGET } = require('../../../adapters/network'); const { @@ -15,8 +14,6 @@ const { defaultRequestConfig, defaultPostRequestConfig, getFieldValueFromMessage, - extractCustomFields, - generateExclusionListUsingKeyPaths, } = require('../../util'); const { CONFIG_CATEGORIES, LIFECYCLE_STAGE_ENDPOINT } = require('./config'); const tags = require('../../util/tags'); @@ -387,33 +384,6 @@ const flattenAddress = (address) => { return result; }; -const populatePayloadWithCustomFields = ( - message, - customPropertyMapping, - payload, - MAPPING_CONFIG, -) => { - const rawPayload = { ...payload }; - const traits = getFieldValueFromMessage(message, 'traits'); - const itemExclusionList = generateExclusionListUsingKeyPaths(MAPPING_CONFIG); - const customField = {}; - if (customPropertyMapping && customPropertyMapping.length > 0) { - const propertyMap = getHashFromArray(customPropertyMapping, 'from', 'to', false); - Object.keys(traits).forEach((key) => { - if (propertyMap[key]) { - itemExclusionList.push(key); - customField[propertyMap[key]] = traits[key]; - } - }); - } - // adding all other trait fields as custom fields, freshsales will handle any non configured fields by itself and reject the values - rawPayload.custom_field = { - ...customField, - ...extractCustomFields(message, {}, ['traits', 'context.traits'], itemExclusionList), - }; - return rawPayload; -}; - module.exports = { getUserAccountDetails, flattenAddress, @@ -421,5 +391,4 @@ module.exports = { UpdateContactWithLifeCycleStage, updateAccountWOContact, getHeaders, - populatePayloadWithCustomFields, }; diff --git a/src/v0/destinations/freshsales/utils.test.js b/src/v0/destinations/freshsales/utils.test.js deleted file mode 100644 index 5f6bd761a9..0000000000 --- a/src/v0/destinations/freshsales/utils.test.js +++ /dev/null @@ -1,91 +0,0 @@ -const { populatePayloadWithCustomFields } = require('./utils'); - -describe('populatePayloadWithCustomFields', () => { - it('Mapping config is empty', () => { - const message = { - traits: { - email: 'test@example.com', - firstName: 'John', - lastName: 'Doe', - newProp: 'newPropValue', - }, - }; - const customPropertyMapping = [{ from: 'newProp', to: 'cf_newProp' }]; - const payload = {}; - const MAPPING_CONFIG = []; - - const result = populatePayloadWithCustomFields( - message, - customPropertyMapping, - payload, - MAPPING_CONFIG, - ); - - expect(result).toEqual({ - custom_field: { - email: 'test@example.com', - firstName: 'John', - lastName: 'Doe', - cf_newProp: 'newPropValue', - }, - }); - }); - - it('should exclude specified fields from being added as custom fields', () => { - const message = { - traits: { - email: 'test@example.com', - first_name: 'John', - lastName: 'Doe', - newProp: 'newPropValue', - }, - }; - const customPropertyMapping = [{ from: 'newProp', to: 'cf_newProp' }]; - const payload = {}; - const MAPPING_CONFIG = [ - { destKey: 'first_name', sourceKeys: 'firstName', sourceFromGenericMap: true }, - { destKey: 'email', sourceKeys: 'email', sourceFromGenericMap: true }, - ]; - - const result = populatePayloadWithCustomFields( - message, - customPropertyMapping, - payload, - MAPPING_CONFIG, - ); - - expect(result).toEqual({ - custom_field: { - cf_newProp: 'newPropValue', - lastName: 'Doe', - }, - }); - }); - - it('should not overwrite existing payload data', () => { - const message = { - traits: { - firstName: 'John', - }, - }; - const customPropertyMapping = [{ from: 'firstName', to: 'first_name' }]; - const initialPayload = { - existingField: 'existingValue', - }; - const MAPPING_CONFIG = []; - - const result = populatePayloadWithCustomFields( - message, - customPropertyMapping, - initialPayload, - MAPPING_CONFIG, - ); - - expect(result).toEqual({ - existingField: 'existingValue', - custom_field: { - first_name: 'John', - }, - }); - }); -}); diff --git a/src/v0/util/index.js b/src/v0/util/index.js index fd2eb1fb61..0cc66b2d7a 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -34,7 +34,6 @@ const { } = require('../../adapters/networkhandler/authConstants'); const { FEATURE_FILTER_CODE, FEATURE_GZIP_SUPPORT } = require('./constant'); const { CommonUtils } = require('../../util/common'); -const genericFieldMapping = require('./data/GenericFieldMapping.json'); // ======================================================================== // INLINERS @@ -1306,55 +1305,6 @@ const generateExclusionList = (mappingConfig) => Array.isArray(mapping.sourceKeys) ? [...mapping.sourceKeys] : [mapping.sourceKeys], ); -/** - * Generates an exclusion list using key paths based on the provided mapping configuration. - * - * @param {Array} mappingConfig - The traditional mapping configuration used build payloads. - * example : - * [ - * { - * "destKey": "email", - * "sourceKeys": "email", - * "required": true, - * "sourceFromGenericMap": true - * }, - * { - * "destKey": "key", - * "sourceKeys": ["context.traits.key", "traits.key"], - * } - * ] - * @returns {Array} - The generated exclusion list, which can be fed in the "extractCustomFields" function. - */ -const generateExclusionListUsingKeyPaths = (mappingConfig) => { - const resultList = mappingConfig.flatMap((mapping) => { - if (mapping.sourceFromGenericMap) { - // If sourceFromGenericMap is true, use the mapping for generic fields - const genericMappings = genericFieldMapping[mapping.sourceKeys]; - if (isDefinedAndNotNull(genericMappings)) { - if (Array.isArray(genericMappings)) { - // Map over the genericMappings to extract the last part of each path - return genericMappings.map((paths) => paths.split('.').pop()); - } - // Handle the case where it's a single string, not an array - return [genericMappings.split('.').pop()]; - } - } - - // Handle the case where sourceFromGenericMap is not true or does not exist - // This also assumes that the non-generic mappings might be in dot notation and extracts the last key - if (isDefinedAndNotNull(mapping.sourceKeys)) { - return Array.isArray(mapping.sourceKeys) - ? mapping.sourceKeys.map((key) => key.split('.').pop()) - : [mapping.sourceKeys.split('.').pop()]; - } - throw new TransformationError('sourceKeys is not defined in the mapping configuration'); - }); - // Use a Set to filter out duplicates, then convert it back to an array - const uniqueResultList = [...new Set(resultList)]; - - return uniqueResultList; -}; - /** * Extract fileds from message with exclusions * Pass the keys of message for extraction and @@ -1391,9 +1341,7 @@ function extractCustomFields(message, payload, keys, exclusionFields) { const messageContext = get(message, key); if (messageContext) { Object.keys(messageContext).forEach((k) => { - if (!exclusionFields.includes(k)) { - mappingKeys.push(k); - } + if (!exclusionFields.includes(k)) mappingKeys.push(k); }); mappingKeys.forEach((mappingKey) => { if (!(typeof messageContext[mappingKey] === 'undefined')) { @@ -2386,5 +2334,4 @@ module.exports = { findExistingBatch, removeDuplicateMetadata, combineBatchRequestsWithSameJobIds, - generateExclusionListUsingKeyPaths, }; diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js index f0e2845d57..4dc6255691 100644 --- a/src/v0/util/index.test.js +++ b/src/v0/util/index.test.js @@ -21,7 +21,6 @@ const functionNames = [ 'removeUndefinedNullValuesAndEmptyObjectArray', 'groupEventsByType', 'isValidInteger', - 'generateExclusionListUsingKeyPaths', ]; // Names of the utility functions to test which expects multiple arguments as values and not objects diff --git a/src/v0/util/testdata/generateExclusionListUsingKeyPaths.json b/src/v0/util/testdata/generateExclusionListUsingKeyPaths.json deleted file mode 100644 index c1cf1c3197..0000000000 --- a/src/v0/util/testdata/generateExclusionListUsingKeyPaths.json +++ /dev/null @@ -1,37 +0,0 @@ -[ - { - "description": "should correctly generate exclusion list for a simple mapping configuration", - "input": [ - [ - { - "destKey": "email", - "sourceKeys": "email", - "required": true, - "sourceFromGenericMap": true - }, - { - "destKey": "key", - "sourceKeys": "context.traits.key" - } - ] - ], - "output": ["email", "id", "key"] - }, - { - "description": "should correctly generate exclusion list for a simple mapping configuration", - "input": [ - [ - { - "destKey": "email", - "sourceKeys": ["context.email", "traits.email"], - "required": true - }, - { - "destKey": "key", - "sourceKeys": ["context.traits.key", "traits.key"] - } - ] - ], - "output": ["email", "key"] - } -] diff --git a/test/integrations/destinations/freshsales/processor/data.ts b/test/integrations/destinations/freshsales/processor/data.ts index b16c3d18c7..eca3b88d9d 100644 --- a/test/integrations/destinations/freshsales/processor/data.ts +++ b/test/integrations/destinations/freshsales/processor/data.ts @@ -189,9 +189,6 @@ export const data = [ work_number: '9988776655', mobile_number: '1-926-555-9504', lifecycle_stage_id: 71010794467, - custom_field: { - owner_id: '70000090119', - }, }, unique_identifier: { emails: 'testuser@google.com', @@ -303,9 +300,6 @@ export const data = [ work_number: '9988776655', mobile_number: '1-926-555-9504', lifecycle_stage_id: 71010794467, - custom_field: { - owner_id: '70000090119', - }, }, unique_identifier: { emails: 'testuser@google.com', @@ -424,7 +418,6 @@ export const data = [ mobile_number: '1-926-555-9504', created_at: '2022-06-22T10:57:58Z', updated_at: '2022-06-22T10:57:58Z', - custom_field: {}, }, unique_identifier: { emails: 'testuser@google.com', @@ -2692,132 +2685,4 @@ export const data = [ }, }, }, - { - name: 'freshsales', - description: 'Identify call for creating new user along with custom property mapping', - feature: 'processor', - module: 'destination', - version: 'v0', - input: { - request: { - body: [ - { - destination: { - Config: { - apiKey: 'dummyApiKey', - domain: 'rudderstack-476952domain3105.myfreshworks.com', - customPropertyMapping: [ - { - from: 'newProp1', - to: 'cf_newProp1', - }, - { - from: 'newProp2', - to: 'cf_newProp2', - }, - ], - }, - }, - message: { - messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', - originalTimestamp: '2022-06-22T10:57:58Z', - anonymousId: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.0', - }, - device: { - advertisingId: 'T0T0T072-5e28-45a1-9eda-ce22a3e36d1a', - id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', - manufacturer: 'Google', - model: 'AOSP on IA Emulator', - name: 'generic_x86_arm', - type: 'ios', - attTrackingStatus: 3, - }, - library: { - name: 'RudderLabs JavaScript SDK', - version: '1.0.0', - }, - locale: 'en-US', - os: { - name: 'iOS', - version: '14.4.1', - }, - screen: { - density: 2, - }, - }, - traits: { - email: 'testuser@google.com', - first_name: 'Rk', - last_name: 'Mishra', - mobileNumber: '1-926-555-9504', - lifecycleStageId: 71010794467, - phone: '9988776655', - owner_id: '70000090119', - newProp1: 'value1', - newProp2: 'value2', - }, - type: 'identify', - sentAt: '2022-04-22T10:57:58Z', - }, - }, - ], - }, - }, - output: { - response: { - status: 200, - body: [ - { - output: { - body: { - XML: {}, - FORM: {}, - JSON: { - contact: { - emails: 'testuser@google.com', - first_name: 'Rk', - last_name: 'Mishra', - work_number: '9988776655', - external_id: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', - mobile_number: '1-926-555-9504', - created_at: '2022-06-22T10:57:58Z', - updated_at: '2022-06-22T10:57:58Z', - lifecycle_stage_id: 71010794467, - custom_field: { - cf_newProp1: 'value1', - cf_newProp2: 'value2', - owner_id: '70000090119', - }, - }, - unique_identifier: { - emails: 'testuser@google.com', - }, - }, - JSON_ARRAY: {}, - }, - type: 'REST', - files: {}, - method: 'POST', - params: {}, - headers: { - 'Content-Type': 'application/json', - Authorization: 'Token token=dummyApiKey', - }, - version: '1', - endpoint: - 'https://rudderstack-476952domain3105.myfreshworks.com/crm/sales/api/contacts/upsert', - userId: '', - }, - statusCode: 200, - }, - ], - }, - }, - }, ]; diff --git a/test/integrations/destinations/freshsales/router/data.ts b/test/integrations/destinations/freshsales/router/data.ts index 8d76a39570..8548d337b3 100644 --- a/test/integrations/destinations/freshsales/router/data.ts +++ b/test/integrations/destinations/freshsales/router/data.ts @@ -115,7 +115,6 @@ export const data = [ zipcode: 'postalCode', created_at: '2022-08-30T11:28:43.647+05:30', updated_at: '2022-08-30T11:28:43.647+05:30', - custom_field: {}, }, unique_identifier: { emails: 'user112@mail.com' }, }, From aef5f8e5f267262e0f9e10229f14f2bcc8ad29e2 Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:39:55 +0530 Subject: [PATCH 18/33] feat: onboard bluecore integration (#3061) * feat: initial commit * feat: finalising track calls * chore: small commit * fix: adding identify call * fix: adding product array support * fix: adding unit test cases * fix: adding check for action field value other than identify * fix: adding test cases for identify response builder * fix: adding support for cdkv2 * fix: adding component test case part 1 * fix: adding component test case part 2 * fix: complete bluecore via CDK v2 * fix: removing the JS code and only have cdk * fix: adding support for externalID feature in distinct id * fix: making email mandatory for optin and unsubscribe events * fix: merging common properties to a single config json * Apply suggestions from code review Co-authored-by: Anant Jain <62471433+anantjain45823@users.noreply.github.com> * fix: using lodash for deep merge * fix: addressed review comments * refactor: bluecore workflow steps for track * refactor: bluecore workflow steps for track * refactor: bluecore workflow steps for track * refactor: populateAccurateDistinctId * Apply suggestions from code review Co-authored-by: Dilip Kola <33080863+koladilip@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Dilip Kola <33080863+koladilip@users.noreply.github.com> * fix: review comments addressed --------- Co-authored-by: Anant Jain <62471433+anantjain45823@users.noreply.github.com> Co-authored-by: Dilip Kola Co-authored-by: Dilip Kola <33080863+koladilip@users.noreply.github.com> --- src/cdk/v2/destinations/bluecore/config.js | 56 ++ .../bluecore/data/bluecoreCommonConfig.json | 52 ++ .../bluecore/data/bluecoreIdentifyConfig.json | 7 + .../bluecore/data/bluecoreTrackConfig.json | 22 + .../destinations/bluecore/procWorkflow.yaml | 69 +++ src/cdk/v2/destinations/bluecore/utils.js | 250 +++++++++ .../v2/destinations/bluecore/utils.test.js | 403 +++++++++++++++ .../destinations/bluecore/data.ts | 6 + .../destinations/bluecore/ecommTestData.ts | 489 ++++++++++++++++++ .../destinations/bluecore/identifyTestData.ts | 381 ++++++++++++++ .../destinations/bluecore/trackTestData.ts | 439 ++++++++++++++++ .../bluecore/validationTestData.ts | 150 ++++++ test/integrations/testUtils.ts | 6 +- 13 files changed, 2327 insertions(+), 3 deletions(-) create mode 100644 src/cdk/v2/destinations/bluecore/config.js create mode 100644 src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json create mode 100644 src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json create mode 100644 src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json create mode 100644 src/cdk/v2/destinations/bluecore/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/bluecore/utils.js create mode 100644 src/cdk/v2/destinations/bluecore/utils.test.js create mode 100644 test/integrations/destinations/bluecore/data.ts create mode 100644 test/integrations/destinations/bluecore/ecommTestData.ts create mode 100644 test/integrations/destinations/bluecore/identifyTestData.ts create mode 100644 test/integrations/destinations/bluecore/trackTestData.ts create mode 100644 test/integrations/destinations/bluecore/validationTestData.ts diff --git a/src/cdk/v2/destinations/bluecore/config.js b/src/cdk/v2/destinations/bluecore/config.js new file mode 100644 index 0000000000..9b9cde9c66 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/config.js @@ -0,0 +1,56 @@ +const { getMappingConfig } = require('../../../../v0/util'); + +const BASE_URL = 'https://api.bluecore.com/api/track/mobile/v1'; + +const CONFIG_CATEGORIES = { + IDENTIFY: { + name: 'bluecoreIdentifyConfig', + type: 'identify', + }, + TRACK: { + name: 'bluecoreTrackConfig', + type: 'track', + }, + COMMON: { + name: 'bluecoreCommonConfig', + type: 'common', + }, +}; + +const EVENT_NAME_MAPPING = [ + { + src: ['product viewed'], + dest: 'viewed_product', + }, + { + src: ['products searched'], + dest: 'search', + }, + { + src: ['product added'], + dest: 'add_to_cart', + }, + { + src: ['product removed'], + dest: 'remove_from_cart', + }, + { + src: ['product added to wishlist'], + dest: 'wishlist', + }, + { + src: ['order completed'], + dest: 'purchase', + }, +]; + +const BLUECORE_EXCLUSION_FIELDS = ['query', 'order_id', 'total']; + +const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); +module.exports = { + CONFIG_CATEGORIES, + MAPPING_CONFIG, + EVENT_NAME_MAPPING, + BASE_URL, + BLUECORE_EXCLUSION_FIELDS, +}; diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json new file mode 100644 index 0000000000..be74c7c4b3 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json @@ -0,0 +1,52 @@ +[ + { + "destKey": "properties.customer.name", + "sourceKeys": "name", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.customer.first_name", + "sourceKeys": "firstName", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.customer.last_name", + "sourceKeys": "lastName", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.customer.age", + "sourceKeys": ["context.traits.age", "traits.age"], + "required": false + }, + { + "destKey": "properties.customer.sex", + "sourceKeys": ["traits.gender", "context.traits.gender", "traits.sex", "context.traits.sex"], + "required": false + }, + { + "destKey": "properties.customer.address", + "sourceKeys": "address", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.customer.email", + "sourceKeys": "email", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.client", + "sourceKeys": "context.app.version", + "required": false + }, + { + "destKey": "properties.device", + "sourceKeys": "context.device.model", + "required": false + } +] diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json new file mode 100644 index 0000000000..5c3686f0ab --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json @@ -0,0 +1,7 @@ +[ + { + "destKey": "event", + "sourceKeys": ["traits.action", "context.traits.action"], + "required": false + } +] diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json new file mode 100644 index 0000000000..8f6d59ec54 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json @@ -0,0 +1,22 @@ +[ + { + "destKey": "properties.search_term", + "sourceKeys": "properties.query", + "required": false + }, + { + "destKey": "properties.order_id", + "sourceKeys": "properties.order_id", + "required": false + }, + { + "destKey": "properties.total", + "sourceKeys": "properties.total", + "required": false + }, + { + "destKey": "properties.products", + "sourceKeys": ["properties.products"], + "required": false + } +] diff --git a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml new file mode 100644 index 0000000000..378659fa2a --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml @@ -0,0 +1,69 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + - name: defaultRequestConfig + path: ../../../../v0/util + - name: removeUndefinedNullValuesAndEmptyObjectArray + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - path: ./utils + - path: lodash + name: cloneDeep + +steps: + - 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.([.TRACK, .IDENTIFY])}}, "message type " + messageType + " is not supported"); + $.assertConfig(.destination.Config.bluecoreNamespace, "[BLUECORE] account namespace required for Authentication."); + - name: prepareIdentifyPayload + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} + template: | + const payload = $.constructProperties(.message); + payload.token = .destination.Config.bluecoreNamespace; + $.verifyPayload(payload, .message); + payload.event = payload.event ?? 'customer_patch'; + payload.properties.distinct_id = $.populateAccurateDistinctId(payload, .message); + $.context.payloads = [$.removeUndefinedAndNullValues(payload)]; + - name: handleTrackEvent + condition: $.outputs.messageType === {{$.EventType.TRACK}} + steps: + - name: validateInput + description: Additional validation for Track events + template: | + $.assert(.message.event, "event_name could not be mapped. Aborting.") + - name: deduceEventNames + template: | + $.context.deducedEventNameArray = $.deduceTrackEventName(.message.event,.destination.Config) + - name: preparePayload + template: | + const payload = $.constructProperties(.message); + $.context.payloads = $.context.deducedEventNameArray@eventName.( + const newPayload = $.cloneDeep(payload); + newPayload.properties.distinct_id = $.populateAccurateDistinctId(newPayload, ^.message); + const temporaryProductArray = newPayload.properties.products ?? $.createProductForStandardEcommEvent(^.message, eventName); + newPayload.properties.products = $.normalizeProductArray(temporaryProductArray); + newPayload.event = eventName; + newPayload.token = ^.destination.Config.bluecoreNamespace; + $.verifyPayload(newPayload, ^.message); + $.removeUndefinedNullValuesAndEmptyObjectArray(newPayload) + )[]; + + - name: buildResponse + template: | + $.context.payloads.( + const response = $.defaultRequestConfig(); + response.body.JSON = .; + response.method = "POST"; + response.endpoint = "https://api.bluecore.com/api/track/mobile/v1"; + response.headers = { + "Content-Type": "application/json" + }; + response + ) diff --git a/src/cdk/v2/destinations/bluecore/utils.js b/src/cdk/v2/destinations/bluecore/utils.js new file mode 100644 index 0000000000..22ec254fe2 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/utils.js @@ -0,0 +1,250 @@ +const lodash = require('lodash'); + +const { + InstrumentationError, + isDefinedAndNotNullAndNotEmpty, + getHashFromArrayWithDuplicate, + isDefinedAndNotNull, + isDefinedNotNullNotEmpty, +} = require('@rudderstack/integrations-lib'); +const { + getFieldValueFromMessage, + validateEventName, + constructPayload, + getDestinationExternalID, +} = require('../../../../v0/util'); +const { CommonUtils } = require('../../../../util/common'); +const { EVENT_NAME_MAPPING } = require('./config'); +const { EventType } = require('../../../../constants'); +const { MAPPING_CONFIG, CONFIG_CATEGORIES } = require('./config'); + +/** + * Verifies the correctness of payload for different events. + * + * @param {Object} payload - The payload object containing event information. + * @param {Object} message - The message object containing additional information. + * @throws {InstrumentationError} - Throws an error if required properties are missing. + * @returns {void} + */ +const verifyPayload = (payload, message) => { + if ( + message.type === EventType.IDENTIFY && + isDefinedNotNullNotEmpty(message.traits?.action) && + message.traits?.action !== 'identify' + ) { + throw new InstrumentationError( + "[Bluecore] traits.action must be 'identify' for identify action", + ); + } + switch (payload.event) { + case 'search': + if (!payload?.properties?.search_term) { + throw new InstrumentationError( + '[Bluecore] property:: search_query is required for search event', + ); + } + break; + case 'purchase': + if (!payload?.properties?.order_id) { + throw new InstrumentationError( + '[Bluecore] property:: order_id is required for purchase event', + ); + } + if (!payload?.properties?.total) { + throw new InstrumentationError( + '[Bluecore] property:: total is required for purchase event', + ); + } + if ( + !isDefinedAndNotNull(payload?.properties?.customer) || + Object.keys(payload.properties.customer).length === 0 + ) { + throw new InstrumentationError( + `[Bluecore] property:: No relevant trait to populate customer information, which is required for ${payload.event} event`, + ); + } + break; + case 'identify': + case 'optin': + case 'unsubscribe': + if (!isDefinedAndNotNullAndNotEmpty(getFieldValueFromMessage(message, 'email'))) { + throw new InstrumentationError( + `[Bluecore] property:: email is required for ${payload.event} action`, + ); + } + if ( + !isDefinedAndNotNull(payload?.properties?.customer) || + Object.keys(payload.properties.customer).length === 0 + ) { + throw new InstrumentationError( + `[Bluecore] property:: No relevant trait to populate customer information, which is required for ${payload.event} action`, + ); + } + break; + default: + break; + } +}; + +/** + * Deduces the track event name based on the provided track event name and configuration. + * + * @param {string} trackEventName - The track event name to deduce. + * @param {object} Config - The configuration object. + * @returns {string|array} - The deduced track event name. + */ +const deduceTrackEventName = (trackEventName, destConfig) => { + let eventName; + const { eventsMapping } = destConfig; + validateEventName(trackEventName); + /* + Step 1: Will look for the event name in the eventsMapping array if mapped to a standard bluecore event. + and return the corresponding event name if found. + */ + if (eventsMapping.length > 0) { + const keyMap = getHashFromArrayWithDuplicate(eventsMapping, 'from', 'to', false); + eventName = keyMap[trackEventName]; + } + if (isDefinedAndNotNullAndNotEmpty(eventName)) { + const finalEvent = typeof eventName === 'string' ? [eventName] : [...eventName]; + return finalEvent; + } + + /* + Step 2: To find if the particular event is amongst the list of standard + Rudderstack ecommerce events, used specifically for Bluecore API + mappings. + */ + + const eventMapInfo = EVENT_NAME_MAPPING.find((eventMap) => + eventMap.src.includes(trackEventName.toLowerCase()), + ); + if (isDefinedAndNotNull(eventMapInfo)) { + return [eventMapInfo.dest]; + } + + // Step 3: if nothing matches this is to be considered as a custom event + return [trackEventName]; +}; + +/** + * Determines if the given event name is a standard Bluecore event. + * + * @param {string} eventName - The name of the event to check. + * @returns {boolean} - True if the event is a standard Bluecore event, false otherwise. + */ +const isStandardBluecoreEvent = (eventName) => { + // Return false immediately if eventName is an empty string or falsy + if (!eventName) { + return false; + } + // Proceed with the original check if eventName is not empty + return !!EVENT_NAME_MAPPING.some((item) => item.dest.includes(eventName)); +}; + +/** + * Adds an array of products to a message. + * + * @param {object} message - The message object to add the products to. + * @param {array|object} products - The array or object of products to add. + * @param {string} eventName - The name of the event. + * @throws {InstrumentationError} - If the products array is not defined or null. + * @returns {array} - The updated product array. + */ +const normalizeProductArray = (products) => { + let finalProductArray = null; + if (isDefinedAndNotNull(products)) { + const productArray = CommonUtils.toArray(products); + const mappedProductArray = productArray.map( + ({ product_id, sku, id, query, order_id, total, ...rest }) => ({ + id: product_id || sku || id, + ...rest, + }), + ); + finalProductArray = mappedProductArray; + } + // if any custom event is not sent with product array, then it should be null + return finalProductArray; +}; + +/** + * Constructs properties based on the given message. + * + * @param {object} message - The message object. + * @returns {object} - The constructed properties object. + */ +const constructProperties = (message) => { + const commonCategory = CONFIG_CATEGORIES.COMMON; + const commonPayload = constructPayload(message, MAPPING_CONFIG[commonCategory.name]); + const category = CONFIG_CATEGORIES[message.type.toUpperCase()]; + const typeSpecificPayload = constructPayload(message, MAPPING_CONFIG[category.name]); + const finalPayload = lodash.merge(commonPayload, typeSpecificPayload); + return finalPayload; +}; + +/** + * Creates a product for a standard e-commerce event. + * + * @param {Object} properties - The properties of the product. + * @param {string} eventName - The name of the event. + * @returns {Array|null} - An array containing the properties if the event is a standard Bluecore event and not 'search', otherwise null. + */ +const createProductForStandardEcommEvent = (message, eventName) => { + const { event, properties } = message; + if (event.toLowerCase() === 'order completed' && eventName === 'purchase') { + throw new InstrumentationError('[Bluecore]:: products array is required for purchase event'); + } + if (eventName !== 'search' && isStandardBluecoreEvent(eventName)) { + return [properties]; + } + return null; +}; +/** + * Function: populateAccurateDistinctId + * + * Description: + * This function is used to populate the accurate distinct ID based on the given payload and message. + * + * Parameters: + * - payload (object): The payload object containing the event and other data. + * - message (object): The message object containing the user data. + * + * Returns: + * - distinctId (string): The accurate distinct ID based on the given payload and message. + * + * Throws: + * - InstrumentationError: If the distinct ID could not be set. + * + */ +const populateAccurateDistinctId = (payload, message) => { + const bluecoreExternalId = getDestinationExternalID(message, 'bluecoreExternalId'); + if (isDefinedAndNotNullAndNotEmpty(bluecoreExternalId)) { + return bluecoreExternalId; + } + let distinctId; + if (payload.event === 'identify') { + distinctId = getFieldValueFromMessage(message, 'userId'); + } else { + // email is always a more preferred distinct_id + distinctId = + getFieldValueFromMessage(message, 'email') || getFieldValueFromMessage(message, 'userId'); + } + + if (!isDefinedAndNotNullAndNotEmpty(distinctId)) { + // dev safe. AnonymouId should be always present + throw new InstrumentationError( + '[Bluecore] property:: distinct_id could not be set. Please provide either email or userId or anonymousId or externalId as distinct_id.', + ); + } + return distinctId; +}; + +module.exports = { + verifyPayload, + deduceTrackEventName, + normalizeProductArray, + isStandardBluecoreEvent, + constructProperties, + createProductForStandardEcommEvent, + populateAccurateDistinctId, +}; diff --git a/src/cdk/v2/destinations/bluecore/utils.test.js b/src/cdk/v2/destinations/bluecore/utils.test.js new file mode 100644 index 0000000000..829073bbcc --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/utils.test.js @@ -0,0 +1,403 @@ +const { + normalizeProductArray, + verifyPayload, + isStandardBluecoreEvent, + deduceTrackEventName, + populateAccurateDistinctId, + createProductForStandardEcommEvent, +} = require('./utils'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); + +describe('normalizeProductArray', () => { + // Adds an array of products to a message when products array is defined and not null. + it('should add an array of products to a message when products array is defined and not null', () => { + const products = [ + { product_id: 1, name: 'Product 1' }, + { product_id: 2, name: 'Product 2' }, + ]; + const eventName = 'purchase'; + + const result = normalizeProductArray(products, eventName); + + expect(result).toEqual([ + { id: 1, name: 'Product 1' }, + { id: 2, name: 'Product 2' }, + ]); + }); + + // Adds a single product object to a message when a single product object is passed. + it('should add a single product object to a message when a single product object is passed', () => { + const product = { product_id: 1, name: 'Product 1' }; + const eventName = 'add_to_cart'; + + const result = normalizeProductArray(product, eventName); + expect(result).toEqual([{ id: 1, name: 'Product 1' }]); + }); + + it('should not throw an InstrumentationError for a custom event when products array is null', () => { + const message = {}; + const products = null; + const eventName = 'custom'; + + expect(() => { + normalizeProductArray(message, products, eventName); + }).toBeNull; + }); +}); + +describe('verifyPayload', () => { + // Verify payload for search event with search_term property. + it('should verify payload for search event with search_term property', () => { + const payload = { + event: 'search', + properties: { + search_term: 'example', + }, + }; + expect(() => verifyPayload(payload, {})).not.toThrow(); + }); + + // Verify payload for purchase event with order_id and total properties. + it('should verify payload for purchase event with order_id and total and customer properties', () => { + const payload = { + event: 'purchase', + properties: { + order_id: '123', + total: 100, + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + // Verify payload for identify event with email property. + it('should verify payload for identify event with email property', () => { + const payload = { + event: 'identify', + properties: { + customer: { + first_name: 'John', + }, + }, + }; + const message = { + traits: { + email: 'test@example.com', + }, + }; + expect(() => verifyPayload(payload, message)).not.toThrow(); + }); + + // Verify payload for search event without search_term property, should throw an InstrumentationError. + it('should throw an InstrumentationError when verifying payload for search event without search_term property', () => { + const payload = { + event: 'search', + properties: {}, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + // Verify payload for purchase event without order_id property, should throw an InstrumentationError. + it('should throw an InstrumentationError when verifying payload for purchase event without order_id property', () => { + const payload = { + event: 'purchase', + properties: { + total: 100, + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + // Verify payload for purchase event without total property, should throw an InstrumentationError. + it('should throw an InstrumentationError when verifying payload for purchase event without total property', () => { + const payload = { + event: 'purchase', + properties: { + order_id: '123', + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + // Verify payload for purchase event without total property, should throw an InstrumentationError. + it('should throw an InstrumentationError when verifying payload for identify event with action field other than identify', () => { + const payload = { + event: 'random', + properties: { + email: 'abc@gmail.com', + }, + }; + expect(() => + verifyPayload(payload, { type: 'identify', traits: { action: 'random' } }), + ).toThrow(InstrumentationError); + }); + + it('should throw an InstrumentationError when verifying payload for optin event without email property', () => { + const payload = { + event: 'optin', + properties: { + order_id: '123', + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + it('should throw an InstrumentationError when verifying payload for unsubscribe event without email property', () => { + const payload = { + event: 'unsubscribe', + properties: { + order_id: '123', + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); +}); + +describe('isStandardBluecoreEvent', () => { + // Returns true if the given event name is in the list of standard Bluecore events. + it('should return true when the given event name is in the list of standard Bluecore events', () => { + const eventName = 'search'; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(true); + }); + + // Returns false if the given event name is not in the list of standard Bluecore events. + it('should return false when the given event name is not in the list of standard Bluecore events', () => { + const eventName = 'someEvent'; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); + + // Returns false if the given event name is null. + it('should return false when the given event name is null', () => { + const eventName = null; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); + + // Returns false if the given event name is undefined. + it('should return false when the given event name is undefined', () => { + const eventName = undefined; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); + + // Returns false if the given event name is not a string. + it('should return false when the given event name is not a string', () => { + const eventName = 123; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); + + // Returns false if the given event name is an empty string. + it('should return false when the given event name is an empty string', () => { + const eventName = ''; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); +}); + +describe('deduceTrackEventName', () => { + // The function returns the trackEventName if no eventsMapping is provided and the trackEventName is not a standard Rudderstack ecommerce event. + it('should return the trackEventName when no eventsMapping is provided and the trackEventName is not a standard Rudderstack ecommerce event', () => { + const trackEventName = 'customEvent'; + const Config = { + eventsMapping: [], + }; + const result = deduceTrackEventName(trackEventName, Config); + expect(result).toEqual([trackEventName]); + }); + + // The function returns the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event. + it('should return the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event', () => { + const trackEventName = 'customEvent'; + const Config = { + eventsMapping: [{ from: 'customEvent', to: 'search' }], + }; + const result = deduceTrackEventName(trackEventName, Config); + expect(result).toEqual(['search']); + }); + + // The function returns the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event. + it('should return the corresponding event name array from eventsMapping if the trackEventName is mapped to more than one standard bluecore events', () => { + const trackEventName = 'customEvent'; + const Config = { + eventsMapping: [ + { from: 'customEvent', to: 'search' }, + { from: 'customEvent', to: 'purchase' }, + ], + }; + const result = deduceTrackEventName(trackEventName, Config); + expect(result).toEqual(['search', 'purchase']); + }); + + // The function returns the corresponding standard Rudderstack ecommerce event name if the trackEventName is a standard bluecore event. + it('should return the corresponding standard Rudderstack ecommerce event name if the trackEventName is a standard bluecore event', () => { + const trackEventName = 'Product Added to Wishlist'; + const Config = { + eventsMapping: [], + }; + const result = deduceTrackEventName(trackEventName, Config); + expect(result).toEqual(['wishlist']); + }); + + // The function throws an error if the trackEventName is not a string. + it('should throw an error if the trackEventName is not a string', () => { + const trackEventName = 123; + const Config = { + eventsMapping: [], + }; + expect(() => deduceTrackEventName(trackEventName, Config)).toThrow(); + }); + + // The function throws an error if the trackEventName is an empty string. + it('should throw an error if the trackEventName is an empty string', () => { + const trackEventName = ''; + const Config = { + eventsMapping: [], + }; + expect(() => deduceTrackEventName(trackEventName, Config)).toThrow(); + }); +}); + +describe('populateAccurateDistinctId', () => { + // Returns the distinctId based on the email field when it exists in the message object and the event is not an identify event. + it('should return the distinctId based on the email field when it exists in the message object and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { userId: '123', context: { traits: { email: 'test@example.com' } } }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('test@example.com'); + }); + + // Returns the distinctId based on the userId field when it exists in the message object and the event is an identify event. + it('should return the distinctId based on the userId field when it exists in the message object and the event is an identify event', () => { + const payload = { event: 'identify' }; + const message = { userId: '123', context: { traits: { email: 'test@example.com' } } }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('123'); + }); + + // Returns the distinctId based on the userId field when it exists in the message object and the email field does not exist and the event is not an identify event. + it('should return the distinctId based on the userId field when it exists in the message object and the email field does not exist and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { userId: '123' }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('123'); + }); + + // Returns the distinctId based on the email field when it exists in the message object and the userId field is empty and the event is not an identify event. + it('should throw instrumenatation error as the message is malformed where email is at the root level', () => { + const payload = { event: 'event' }; + const message = { email: 'test@example.com', userId: '' }; + const testFn = () => populateAccurateDistinctId(payload, message); + expect(testFn).toThrow(InstrumentationError); + }); + + // Returns the distinctId based on the userId field when it exists in the message object and the email field is empty and the event is not an identify event. + it('should return the distinctId based on the userId field when it exists in the message object and the email field is empty and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { email: '', userId: '123' }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('123'); + }); + + // Returns the distinctId based on the anonymousId field when it exists in the message object and the email and userId fields are empty and the event is not an identify event. + it('should return the distinctId based on the anonymousId field when it exists in the message object and the email and userId fields are empty and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { anonymousId: 'abc' }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('abc'); + }); + + it('should return the distinctId based on the externalId field when it exists in the context object and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { + userId: '123', + context: { + traits: { email: 'test@example.com' }, + externalId: [{ type: 'bluecoreExternalId', id: '54321' }], + }, + }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('54321'); + }); + + it('should return the distinctId based on the externalId field when it exists in the context object and the event is an identify event', () => { + const payload = { event: 'identify' }; + const message = { + userId: '123', + context: { + traits: { email: 'test@example.com' }, + externalId: [{ type: 'bluecoreExternalId', id: '54321' }], + }, + }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('54321'); + }); +}); + +describe('createProductForStandardEcommEvent', () => { + // Returns an array containing the properties if the event is a standard Bluecore event and not 'search'. + it("should return an array containing the properties when the event is a standard Bluecore event and not 'search'", () => { + const message = { + event: 'some event', + properties: { name: 'product 1' }, + }; + const eventName = 'some event'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toEqual(null); + }); + + // Returns null if the event is 'search'. + it("should return null when the event is 'search'", () => { + const message = { + event: 'search', + properties: { name: 'product 1' }, + }; + const eventName = 'search'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); + + // Throws an InstrumentationError if the event is 'order completed' and the eventName is 'purchase'. + it("should throw an InstrumentationError when the event is 'order completed' and the eventName is 'purchase'", () => { + const message = { + event: 'order completed', + properties: { name: 'product 1' }, + }; + const eventName = 'purchase'; + expect(() => { + createProductForStandardEcommEvent(message, eventName); + }).toThrow(InstrumentationError); + }); + + // Returns null if the eventName is not a standard Bluecore event. + it('should return null when the eventName is not a standard Bluecore event', () => { + const message = { + event: 'some event', + properties: { name: 'product 1', products: [{ product_id: 1, name: 'prod1' }] }, + }; + const eventName = 'non-standard'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); + + // Returns null if the eventName is not provided. + it('should return null when the eventName is not provided', () => { + const message = { + event: 'some event', + properties: { name: 'product 1' }, + }; + const result = createProductForStandardEcommEvent(message); + expect(result).toBeNull(); + }); + + // Returns null if the properties are not provided. + it('should return null when the properties are not provided', () => { + const message = { + event: 'some event', + }; + const eventName = 'some event'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); +}); diff --git a/test/integrations/destinations/bluecore/data.ts b/test/integrations/destinations/bluecore/data.ts new file mode 100644 index 0000000000..c2205c25a1 --- /dev/null +++ b/test/integrations/destinations/bluecore/data.ts @@ -0,0 +1,6 @@ +import { ecomTestData } from './ecommTestData'; +import { identifyData } from './identifyTestData'; +import { trackTestData } from './trackTestData'; +import { validationTestData } from './validationTestData'; + +export const data = [...identifyData, ...trackTestData, ...ecomTestData, ...validationTestData]; diff --git a/test/integrations/destinations/bluecore/ecommTestData.ts b/test/integrations/destinations/bluecore/ecommTestData.ts new file mode 100644 index 0000000000..de7584df78 --- /dev/null +++ b/test/integrations/destinations/bluecore/ecommTestData.ts @@ -0,0 +1,489 @@ +import { generateSimplifiedTrackPayload, transformResultBuilder } from '../../testUtils'; + +const metadata = { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', +}; + +const destination = { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + bluecoreNamespace: 'dummy_sandbox', + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + { + from: 'testPurchase', + to: 'purchase', + }, + { + from: 'testboth', + to: 'wishlist', + }, + { + from: 'testboth', + to: 'add_to_cart', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, +}; + +const commonTraits = { + id: 'user@1', + age: '22', + anonymousId: '9c6bd77ea9da3e68', +}; + +const commonPropsWithProducts = { + property1: 'value1', + property2: 'value2', + products: [ + { + product_id: '123', + sku: 'sku123', + name: 'Product 1', + price: 100, + quantity: 2, + }, + { + product_id: '124', + sku: 'sku124', + name: 'Product 2', + price: 200, + quantity: 3, + }, + ], +}; + +const commonPropsWithoutProducts = { + property1: 'value1', + property2: 'value2', + product_id: '123', +}; + +const commonOutputHeaders = { + 'Content-Type': 'application/json', +}; + +const eventEndPoint = 'https://api.bluecore.com/api/track/mobile/v1'; + +export const ecomTestData = [ + { + id: 'bluecore-track-test-1', + name: 'bluecore', + description: + 'Track event call with custom event mapped in destination config to purchase event. This will fail as order_id is not present in the payload', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'testPurchase', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore] property:: order_id is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: order_id is required for purchase event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-2', + name: 'bluecore', + description: + 'Track event call with custom event mapped in destination config to purchase event. This will fail as total is not present in the payload', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'testPurchase', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: { ...commonPropsWithProducts, order_id: '123' }, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore] property:: total is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: total is required for purchase event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-3', + name: 'bluecore', + description: + 'Track event call with products searched event not mapped in destination config. This will fail as search_query is not present in the payload', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'Products Searched', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: { ...commonPropsWithoutProducts }, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore] property:: search_query is required for search event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: search_query is required for search event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-4', + name: 'bluecore', + description: + 'Track event call with Product Viewed event not mapped in destination config. This will be sent with viewed_product name. This event without properties.products will add entire property object as products as this event type is recommended to sent with products', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'product viewed', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + products: [ + { + id: '123', + property1: 'value1', + property2: 'value2', + }, + ], + }, + event: 'viewed_product', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-5', + name: 'bluecore', + description: + 'Track event call with custom event mapped with two standard ecomm events in destination config. Both of the two corresponding standard events will be sent ', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'testboth', + sentAt: '2020-08-14T05:30:30.118Z', + channel: 'web', + context: { + source: 'test', + userAgent: 'chrome', + traits: { + id: 'user@1', + age: '22', + anonymousId: '9c6bd77ea9da3e68', + }, + device: { + advertisingId: 'abc123', + }, + library: { + name: 'rudder-sdk-ruby-sync', + version: '1.0.6', + }, + }, + properties: { + property1: 'value1', + property2: 'value2', + product_id: '123', + }, + anonymousId: 'new-id', + integrations: { + All: true, + }, + }, + metadata, + destination, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'user@1', + customer: { + age: '22', + }, + products: [ + { + id: '123', + property1: 'value1', + property2: 'value2', + }, + ], + }, + event: 'wishlist', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'user@1', + customer: { + age: '22', + }, + products: [ + { + id: '123', + property1: 'value1', + property2: 'value2', + }, + ], + }, + event: 'add_to_cart', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-6', + name: 'bluecore', + description: + 'Track event call with Order Completed event without product array and not mapped in destination config. This will be sent with purchase name. This event without properties.products will generate error as products array is required for purchase event and ordered completed is a standard ecomm event', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'Order Completed', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore]:: products array is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore]:: products array is required for purchase event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bluecore/identifyTestData.ts b/test/integrations/destinations/bluecore/identifyTestData.ts new file mode 100644 index 0000000000..660e335bc6 --- /dev/null +++ b/test/integrations/destinations/bluecore/identifyTestData.ts @@ -0,0 +1,381 @@ +import { + overrideDestination, + transformResultBuilder, + generateSimplifiedIdentifyPayload, +} from '../../testUtils'; + +const destination = { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + bluecoreNamespace: 'dummy_sandbox', + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, +}; + +const metadata = { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', +}; + +const commonTraits = { + anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1', + phone: '+1234589947', + gender: 'non-binary', + db: '19950715', + lastname: 'Rudderlabs', + firstName: 'Test', + address: { + city: 'Kolkata', + state: 'WB', + zip: '700114', + country: 'IN', + }, +}; + +const contextWithExternalId = { + traits: { + ...commonTraits, + email: 'abc@gmail.com', + }, + externalId: [{ type: 'bluecoreExternalId', id: '54321' }], +}; + +const commonOutputCustomerProperties = { + first_name: 'Test', + last_name: 'Rudderlabs', + sex: 'non-binary', + address: { + city: 'Kolkata', + state: 'WB', + zip: '700114', + country: 'IN', + }, +}; + +const commonOutputHeaders = { + 'Content-Type': 'application/json', +}; + +const anonymousId = '97c46c81-3140-456d-b2a9-690d70aaca35'; +const userId = 'user@1'; +const sentAt = '2021-01-03T17:02:53.195Z'; +const originalTimestamp = '2021-01-03T17:02:53.193Z'; +const commonEndpoint = 'https://api.bluecore.com/api/track/mobile/v1'; + +export const identifyData = [ + { + id: 'bluecore-identify-test-1', + name: 'bluecore', + description: + '[Success]: Identify call with all properties, that creates a customer in bluecore by default', + scenario: 'Business', + successCriteria: + 'Response should containt one payload with event name as customer_patch and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: { + traits: { ...commonTraits, email: 'abc@gmail.com' }, + }, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + userId: '', + method: 'POST', + endpoint: commonEndpoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'abc@gmail.com', + customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' }, + }, + token: 'dummy_sandbox', + event: 'customer_patch', + }, + }), + metadata: { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-identify-test-2', + name: 'bluecore', + description: + '[Success]: Identify call with all properties,along with action as identify that mandatorily needs email to link distict_id with customer in bluecore', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: { + traits: commonTraits, + }, + traits: { + action: 'identify', + }, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore] property:: email is required for identify action: Workflow: procWorkflow, Step: prepareIdentifyPayload, ChildStep: undefined, OriginalError: [Bluecore] property:: email is required for identify action', + metadata: { + destinationId: '', + destinationType: '', + namespace: '', + sourceType: '', + }, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-identify-test-3', + name: 'bluecore', + description: + '[Success]: Identify call with all properties,along with action as random which is not supported by bluecore for identify action', + scenario: 'Business', + successCriteria: + 'Response should containt one payload with event name as identify and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: { + traits: commonTraits, + }, + traits: { + action: 'random', + }, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + "[Bluecore] traits.action must be 'identify' for identify action: Workflow: procWorkflow, Step: prepareIdentifyPayload, ChildStep: undefined, OriginalError: [Bluecore] traits.action must be 'identify' for identify action", + metadata: { + destinationId: '', + destinationType: '', + namespace: '', + sourceType: '', + }, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-identify-test-4', + name: 'bluecore', + description: + '[Success]: Identify call with all properties, that stitches a customer email with distinct_id in bluecore if action is identify and email is present in traits', + scenario: 'Business', + successCriteria: + 'Response should containt one payload with event name as identify and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: { + traits: { ...commonTraits, email: 'abc@gmail.com' }, + }, + traits: { + action: 'identify', + }, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + userId: '', + method: 'POST', + endpoint: commonEndpoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'user@1', + customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' }, + }, + token: 'dummy_sandbox', + event: 'identify', + }, + }), + metadata: { + destinationId: '', + destinationType: '', + namespace: '', + sourceType: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-identify-test-5', + name: 'bluecore', + description: + '[Success]: Identify call with all properties and externalId, that creates a customer in bluecore by default, distinct_id is set to externalId value', + scenario: 'Business', + successCriteria: + 'Response should containt one payload with event name as customer_patch and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: contextWithExternalId, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + userId: '', + method: 'POST', + endpoint: commonEndpoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: '54321', + customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' }, + }, + token: 'dummy_sandbox', + event: 'customer_patch', + }, + }), + metadata: { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bluecore/trackTestData.ts b/test/integrations/destinations/bluecore/trackTestData.ts new file mode 100644 index 0000000000..72d48bf93d --- /dev/null +++ b/test/integrations/destinations/bluecore/trackTestData.ts @@ -0,0 +1,439 @@ +import { + generateSimplifiedTrackPayload, + generateTrackPayload, + overrideDestination, + transformResultBuilder, +} from '../../testUtils'; + +const destination = { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + bluecoreNamespace: 'dummy_sandbox', + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + { + from: 'testPurchase', + to: 'purchase', + }, + { + from: 'testboth', + to: 'wishlist', + }, + { + from: 'testboth', + to: 'add_to_cart', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, +}; + +const metadata = { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', +}; + +const commonTraits = { + id: 'user@1', + age: '22', + anonymousId: '9c6bd77ea9da3e68', +}; + +const contextWithExternalId = { + traits: { + ...commonTraits, + email: 'abc@gmail.com', + }, + externalId: [{ type: 'bluecoreExternalId', id: '54321' }], +}; + +const commonPropsWithProducts = { + property1: 'value1', + property2: 'value2', + products: [ + { + product_id: '123', + sku: 'sku123', + name: 'Product 1', + price: 100, + quantity: 2, + }, + { + product_id: '124', + sku: 'sku124', + name: 'Product 2', + price: 200, + quantity: 3, + }, + ], +}; + +const commonPropsWithoutProducts = { + property1: 'value1', + property2: 'value2', + product_id: '123', +}; + +const commonOutputHeaders = { + 'Content-Type': 'application/json', +}; + +const eventEndPoint = 'https://api.bluecore.com/api/track/mobile/v1'; + +export const trackTestData = [ + { + id: 'bluecore-track-test-1', + name: 'bluecore', + description: + 'Track event call with custom event with properties not mapped in destination config. This will be sent with its original name', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'TestEven001', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + products: [ + { + name: 'Product 1', + price: 100, + id: '123', + quantity: 2, + }, + { + name: 'Product 2', + price: 200, + id: '124', + quantity: 3, + }, + ], + }, + event: 'TestEven001', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-2', + name: 'bluecore', + description: + 'Track event call with custom event without properties not mapped in destination config. This will be sent with its original name', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200. As the event paylaod does not contains products, product array will not be sent', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'TestEven001', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + }, + event: 'TestEven001', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-3', + name: 'bluecore', + description: + 'optin event is also considered as a track event, user need to not map it from the UI , it will be sent with the same event name to bluecore', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'optin', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + }, + event: 'optin', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-4', + name: 'bluecore', + description: + 'unsubscribe event is also considered as a track event, user need to not map it from the UI , it will be sent with the same event name to bluecore', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'unsubscribe', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + }, + event: 'unsubscribe', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-5', + name: 'bluecore', + description: + 'Track event call with with externalId. This will map externalId to distinct_id in the payload', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'TestEven001', + userId: 'sajal12', + context: contextWithExternalId, + properties: commonPropsWithProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: '54321', + customer: { + age: '22', + email: 'abc@gmail.com', + }, + products: [ + { + name: 'Product 1', + price: 100, + id: '123', + quantity: 2, + }, + { + name: 'Product 2', + price: 200, + id: '124', + quantity: 3, + }, + ], + }, + event: 'TestEven001', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bluecore/validationTestData.ts b/test/integrations/destinations/bluecore/validationTestData.ts new file mode 100644 index 0000000000..5b81f8c95a --- /dev/null +++ b/test/integrations/destinations/bluecore/validationTestData.ts @@ -0,0 +1,150 @@ +const metadata = { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', +}; + +const outputStatTags = { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', +}; + +export const validationTestData = [ + { + id: 'bluecore-validation-test-1', + name: 'bluecore', + description: '[Error]: Check for unsupported message type', + scenario: 'Framework', + successCriteria: + 'Response should contain error message and status code should be 400, as we are sending a message type which is not supported by bluecore destination and the error message should be Event type random is not supported', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + bluecoreNamespace: 'dummy_sandbox', + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, + }, + metadata, + message: { + userId: 'user123', + type: 'random', + groupId: 'XUepkK', + traits: { + subscribe: true, + }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: 'email', + }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'message type random is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type random is not supported', + metadata, + statTags: outputStatTags, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-validation-test-1', + name: 'bluecore', + description: '[Error]: Check for not finding bluecoreNamespace', + scenario: 'Framework', + successCriteria: + 'Response should contain error message and status code should be 400, as bluecoreNamespace is not found in the destination config', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, + }, + metadata: metadata, + message: { + userId: 'user123', + type: 'random', + groupId: 'XUepkK', + traits: { + subscribe: true, + }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: 'email', + }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'message type random is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type random is not supported', + metadata, + statTags: outputStatTags, + statusCode: 400, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index 1eb1f692aa..2cfbe3be8e 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -49,7 +49,7 @@ export const addMock = (mock: MockAdapter, axiosMock: MockHttpCallsData) => { switch (method.toLowerCase()) { case 'get': - // We are accepting parameters exclusively for mocking purposes and do not require a request body, + // We are accepting parameters exclusively for mocking purposes and do not require a request body, // particularly for GET requests where it is typically unnecessary // @ts-ignore mock.onGet(url, { params }, headersAsymMatch).reply(status, data, headers); @@ -125,7 +125,7 @@ export const generateSimplifiedIdentifyPayload = (parametersOverride: any) => { rudderId: parametersOverride.rudderId || generateAlphanumericId(36), messageId: parametersOverride.messageId || generateAlphanumericId(36), context: { - externalId: parametersOverride.externalId, + externalId: parametersOverride.context.externalId, traits: parametersOverride.context.traits, }, anonymousId: parametersOverride.anonymousId || 'default-anonymousId', @@ -180,7 +180,7 @@ export const generateSimplifiedTrackPayload = (parametersOverride: any) => { rudderId: parametersOverride.rudderId || generateAlphanumericId(36), messageId: parametersOverride.messageId || generateAlphanumericId(36), context: removeUndefinedAndNullValues({ - externalId: parametersOverride.externalId, + externalId: parametersOverride.context.externalId, traits: parametersOverride.context.traits, }), anonymousId: parametersOverride.anonymousId || 'default-anonymousId', From 38f42e11348f1f5c110c122282ead80e86aafed1 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 19 Feb 2024 07:18:35 +0000 Subject: [PATCH 19/33] chore(release): 1.56.0 --- CHANGELOG.md | 23 +++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08032253ac..e3d7755680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.56.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.55.0...v1.56.0) (2024-02-19) + + +### Features + +* add custom property mapping feature for freshsales identify call ([#3065](https://github.com/rudderlabs/rudder-transformer/issues/3065)) ([aa4b8b4](https://github.com/rudderlabs/rudder-transformer/commit/aa4b8b471a647544a3e34dad335f24bec9d9d06b)) +* **hs:** chunking data based on batch limit ([#2907](https://github.com/rudderlabs/rudder-transformer/issues/2907)) ([a60694c](https://github.com/rudderlabs/rudder-transformer/commit/a60694cef1da31d27a5cf90264548cad793f556f)) +* onboard bluecore integration ([#3061](https://github.com/rudderlabs/rudder-transformer/issues/3061)) ([aef5f8e](https://github.com/rudderlabs/rudder-transformer/commit/aef5f8e5f267262e0f9e10229f14f2bcc8ad29e2)) +* tiktok_offline_events added support for all Standard events ([#3094](https://github.com/rudderlabs/rudder-transformer/issues/3094)) ([b5cdccb](https://github.com/rudderlabs/rudder-transformer/commit/b5cdccb75fe68150816140174087fddad677db10)) + + +### Bug Fixes + +* add support of placing properties at root in af ([#3082](https://github.com/rudderlabs/rudder-transformer/issues/3082)) ([0f01524](https://github.com/rudderlabs/rudder-transformer/commit/0f01524b6f4f2f82efc21f88f8c97cb6fdaf91ea)) +* amplitude batch output metadata ([#3077](https://github.com/rudderlabs/rudder-transformer/issues/3077)) ([69c8348](https://github.com/rudderlabs/rudder-transformer/commit/69c83489c85486c9b2aed4a1292bd9f0aae9ca44)) +* amplitude: Error handling for missing event type ([#3079](https://github.com/rudderlabs/rudder-transformer/issues/3079)) ([f7ec0a1](https://github.com/rudderlabs/rudder-transformer/commit/f7ec0a1244a7b97e6b40de5ed9881c63300866dc)) +* custify user-regulation logic ([#3076](https://github.com/rudderlabs/rudder-transformer/issues/3076)) ([9683161](https://github.com/rudderlabs/rudder-transformer/commit/9683161612c7e3b9c2be95a2728f68ec7dcf69f4)) +* error handling for auth0 source ([#3038](https://github.com/rudderlabs/rudder-transformer/issues/3038)) ([2a21274](https://github.com/rudderlabs/rudder-transformer/commit/2a21274333350c615991f7b56b81b766502d5bf4)) +* **ga4:** failures not considered with 200 status in events tab ([#3089](https://github.com/rudderlabs/rudder-transformer/issues/3089)) ([6a364fb](https://github.com/rudderlabs/rudder-transformer/commit/6a364fba34c46b15c0fe4b06ecfa6f4b81b6f436)) +* gaoc batching order ([#3064](https://github.com/rudderlabs/rudder-transformer/issues/3064)) ([a98cabd](https://github.com/rudderlabs/rudder-transformer/commit/a98cabdfe7781ada12baf742df4a3a439fc5fecd)) +* resolve bugsnag issue caused due to undefined properties ([#3086](https://github.com/rudderlabs/rudder-transformer/issues/3086)) ([d522b35](https://github.com/rudderlabs/rudder-transformer/commit/d522b35c908a9f262ba3ba27dda0ea5d9ac5bc6b)) +* tiktok ads v2 error handling ([#3084](https://github.com/rudderlabs/rudder-transformer/issues/3084)) ([b6edff4](https://github.com/rudderlabs/rudder-transformer/commit/b6edff46fa0e0e210e82206fea46a064e3fbe00f)) + ## [1.55.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.54.4...v1.55.0) (2024-02-05) diff --git a/package-lock.json b/package-lock.json index d32e378d2d..10acb2b040 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rudder-transformer", - "version": "1.55.0", + "version": "1.56.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rudder-transformer", - "version": "1.55.0", + "version": "1.56.0", "license": "ISC", "dependencies": { "@amplitude/ua-parser-js": "0.7.24", diff --git a/package.json b/package.json index 0d8e528342..a547ca7a87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rudder-transformer", - "version": "1.55.0", + "version": "1.56.0", "description": "", "homepage": "https://github.com/rudderlabs/rudder-transformer#readme", "bugs": { From b618f67d3c38151968dadb58a142ca126ea51a42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 13:01:37 +0530 Subject: [PATCH 20/33] chore(deps): bump codecov/codecov-action from 3.1.4 to 4.0.1 (#3056) * chore(deps): bump codecov/codecov-action from 3.1.4 to 4.0.1 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.4 to 4.0.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3.1.4...v4.0.1) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore: add codecov token to the action env --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sai Kumar Battinoju Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> --- .github/workflows/dt-test-and-report-code-coverage.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dt-test-and-report-code-coverage.yml b/.github/workflows/dt-test-and-report-code-coverage.yml index 4096227400..ee457f2421 100644 --- a/.github/workflows/dt-test-and-report-code-coverage.yml +++ b/.github/workflows/dt-test-and-report-code-coverage.yml @@ -40,12 +40,14 @@ jobs: npm run lint:fix - name: Upload Coverage Reports to Codecov - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4.0.1 with: directory: ./reports/coverage - name: Upload TS Coverage Reports to Codecov - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4.0.1 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: directory: ./reports/ts-coverage From 314149dda6ea4697c70dff4bb96f4e2e292779a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 13:02:05 +0530 Subject: [PATCH 21/33] chore(deps): bump actions/setup-node from 4.0.1 to 4.0.2 (#3078) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4.0.1...v4.0.2) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/component-test-report.yml | 2 +- .github/workflows/draft-new-release.yml | 2 +- .github/workflows/dt-test-and-report-code-coverage.yml | 2 +- .github/workflows/publish-new-release.yml | 2 +- .github/workflows/ut-tests.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/component-test-report.yml b/.github/workflows/component-test-report.yml index 9904004417..3c93a3b7dc 100644 --- a/.github/workflows/component-test-report.yml +++ b/.github/workflows/component-test-report.yml @@ -28,7 +28,7 @@ jobs: fetch-depth: 1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 33b0396705..c69a481545 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/dt-test-and-report-code-coverage.yml b/.github/workflows/dt-test-and-report-code-coverage.yml index ee457f2421..51a9f8c9ee 100644 --- a/.github/workflows/dt-test-and-report-code-coverage.yml +++ b/.github/workflows/dt-test-and-report-code-coverage.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index 305e27673e..233e99577d 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -30,7 +30,7 @@ jobs: fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/ut-tests.yml b/.github/workflows/ut-tests.yml index 30c29ceaee..0a2ef8a390 100644 --- a/.github/workflows/ut-tests.yml +++ b/.github/workflows/ut-tests.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' From bb60c9cb9a60f2e863a1b2c9c3f2ccd35d8e60f1 Mon Sep 17 00:00:00 2001 From: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:31:43 +0530 Subject: [PATCH 22/33] chore: prevent repeated test report comments on the pr (#3099) * chore: prevent repeated test report comments on the pr * fix: add await keyword * fix: use github paginate * chore: remove unwanted logging --- .github/workflows/component-test-report.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/component-test-report.yml b/.github/workflows/component-test-report.yml index 3c93a3b7dc..e41ca0a723 100644 --- a/.github/workflows/component-test-report.yml +++ b/.github/workflows/component-test-report.yml @@ -40,13 +40,11 @@ jobs: run: | npm run test:ts -- component - - name: Uplaod Report to S3 run: | aws s3 cp ./test_reports/ s3://test-integrations-dev/integrations-test-reports/rudder-transformer/${{ github.event.number }}/ --recursive - - - name: Comment on PR with S3 Object URL + - name: Add Test Report Link as Comment on PR uses: actions/github-script@v7 with: github-token: ${{ secrets.PAT }} @@ -55,10 +53,23 @@ jobs: // Get the pull request number const prNumber = context.payload.pull_request.number; const commentBody = `Test report for this run is available at: https://test-integrations-dev.s3.amazonaws.com/integrations-test-reports/rudder-transformer/${prNumber}/test-report.html`; - + + // find all the comments of the PR + const issueComments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: prNumber, + }); + + for (const comment of issueComments) { + if (comment.body === commentBody) { + console.log('Comment already exists'); + return; + } + } // Comment on the pull request - github.rest.issues.createComment({ + await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, From 096095e5b0066a6b3fcd55b2c5e545f672ccc466 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Tue, 20 Feb 2024 10:55:54 +0530 Subject: [PATCH 23/33] fix: clean up changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3d7755680..d67c45a147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,6 @@ All notable changes to this project will be documented in this file. See [standa ### Features -* add custom property mapping feature for freshsales identify call ([#3065](https://github.com/rudderlabs/rudder-transformer/issues/3065)) ([aa4b8b4](https://github.com/rudderlabs/rudder-transformer/commit/aa4b8b471a647544a3e34dad335f24bec9d9d06b)) * **hs:** chunking data based on batch limit ([#2907](https://github.com/rudderlabs/rudder-transformer/issues/2907)) ([a60694c](https://github.com/rudderlabs/rudder-transformer/commit/a60694cef1da31d27a5cf90264548cad793f556f)) * onboard bluecore integration ([#3061](https://github.com/rudderlabs/rudder-transformer/issues/3061)) ([aef5f8e](https://github.com/rudderlabs/rudder-transformer/commit/aef5f8e5f267262e0f9e10229f14f2bcc8ad29e2)) * tiktok_offline_events added support for all Standard events ([#3094](https://github.com/rudderlabs/rudder-transformer/issues/3094)) ([b5cdccb](https://github.com/rudderlabs/rudder-transformer/commit/b5cdccb75fe68150816140174087fddad677db10)) From af56a0afbce3e6ff3a5396e4e8833d6e0e405fed Mon Sep 17 00:00:00 2001 From: Mihir Bhalala <77438541+mihir-4116@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:45:49 +0530 Subject: [PATCH 24/33] chore: onboard ga4 proxy tests (#3107) --- .../destinations/ga4/dataDelivery/data.ts | 177 ++++++++++++++++++ test/integrations/destinations/ga4/network.ts | 119 ++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 test/integrations/destinations/ga4/dataDelivery/data.ts create mode 100644 test/integrations/destinations/ga4/network.ts diff --git a/test/integrations/destinations/ga4/dataDelivery/data.ts b/test/integrations/destinations/ga4/dataDelivery/data.ts new file mode 100644 index 0000000000..9ccf35e2a1 --- /dev/null +++ b/test/integrations/destinations/ga4/dataDelivery/data.ts @@ -0,0 +1,177 @@ +export const data = [ + { + name: 'ga4', + description: 'Successful data delivery', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://www.google-analytics.com/mp/collect', + headers: { + HOST: 'www.google-analytics.com', + 'Content-Type': 'application/json', + }, + params: { + api_secret: 'dummyApiSecret', + measurement_id: 'dummyMeasurementId', + }, + body: { + JSON: { + client_id: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + timestamp_micros: 1650950229000000, + non_personalized_ads: true, + events: [ + { + name: 'view_item_list', + params: { + item_list_id: 'related_products', + item_list_name: 'Related_products', + items: [ + { + item_id: '507f1f77bcf86cd799439011', + item_name: 'Monopoly: 3rd Edition', + coupon: 'SUMMER_FUN', + item_category: 'Apparel', + item_brand: 'Google', + item_variant: 'green', + price: 19, + quantity: 2, + index: 1, + affiliation: 'Google Merchandise Store', + currency: 'USD', + discount: 2.22, + item_category2: 'Adult', + item_category3: 'Shirts', + item_category4: 'Crew', + item_category5: 'Short sleeve', + item_list_id: 'related_products', + item_list_name: 'Related Products', + location_id: 'L_12345', + }, + ], + engagement_time_msec: 1, + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + }, + }, + output: { + response: { + status: 200, + body: { + output: { + destinationResponse: { + response: { + validationMessages: [], + }, + status: 200, + }, + message: '[GA4 Response Handler] - Request Processed Successfully', + status: 200, + }, + }, + }, + }, + }, + { + name: 'ga4', + description: 'Data delivery failure', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://www.google-analytics.com/debug/mp/collect', + headers: { + HOST: 'www.google-analytics.com', + 'Content-Type': 'application/json', + }, + params: { + api_secret: 'dummyApiSecret', + measurement_id: 'dummyMeasurementId', + }, + body: { + JSON: { + client_id: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + timestamp_micros: 1650950229000000, + non_personalized_ads: true, + events: [ + { + name: 'view_item', + params: { + category: 'Electronics', + productID: 'ABC123', + productName: 'Example Product', + customer_name: 'Sample User', + link_imageURL: 'https://example.com/images/product.jpg', + customer_email: 'testrudder@gmail.com', + link_productURL: 'https://example.com/products/ABC123', + stockAvailability: true, + details_features_0: 'wireless charging', + details_features_1: 'water-resistant', + engagement_time_msec: 1, + transaction_currency: 'USD', + customer_loyaltyPoints: 500, + transaction_totalAmount: 150.99, + transaction_discountApplied: 20.5, + details_specifications_color: 'blue', + details_specifications_specifications_specifications_specifications_color: + 'blue', + details_specifications_specifications_specifications_specifications_weight: + '1.5kg', + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + }, + }, + output: { + response: { + status: 400, + body: { + output: { + destinationResponse: + 'The event param [string_value: "1.5kg"] has a duplicate name [details_specifications_specifications_specifications_specifications_weight].', + message: + 'Validation Server Response Handler:: Validation Error for ga4 of field path :events.params | NAME_DUPLICATED-The event param [string_value: "1.5kg"] has a duplicate name [details_specifications_specifications_specifications_specifications_weight].', + statTags: { + destType: 'GA4', + destinationId: 'Non-determininable', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'Non-determininable', + }, + status: 400, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/ga4/network.ts b/test/integrations/destinations/ga4/network.ts new file mode 100644 index 0000000000..e8c91ef451 --- /dev/null +++ b/test/integrations/destinations/ga4/network.ts @@ -0,0 +1,119 @@ +export const networkCallsData = [ + { + httpReq: { + url: 'https://www.google-analytics.com/mp/collect', + headers: { + HOST: 'www.google-analytics.com', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + params: { + api_secret: 'dummyApiSecret', + measurement_id: 'dummyMeasurementId', + }, + data: { + client_id: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + timestamp_micros: 1650950229000000, + non_personalized_ads: true, + events: [ + { + name: 'view_item_list', + params: { + item_list_id: 'related_products', + item_list_name: 'Related_products', + items: [ + { + item_id: '507f1f77bcf86cd799439011', + item_name: 'Monopoly: 3rd Edition', + coupon: 'SUMMER_FUN', + item_category: 'Apparel', + item_brand: 'Google', + item_variant: 'green', + price: 19, + quantity: 2, + index: 1, + affiliation: 'Google Merchandise Store', + currency: 'USD', + discount: 2.22, + item_category2: 'Adult', + item_category3: 'Shirts', + item_category4: 'Crew', + item_category5: 'Short sleeve', + item_list_id: 'related_products', + item_list_name: 'Related Products', + location_id: 'L_12345', + }, + ], + engagement_time_msec: 1, + }, + }, + ], + }, + method: 'POST', + }, + httpRes: { + data: { + validationMessages: [], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://www.google-analytics.com/debug/mp/collect', + headers: { + HOST: 'www.google-analytics.com', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + params: { + api_secret: 'dummyApiSecret', + measurement_id: 'dummyMeasurementId', + }, + data: { + client_id: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + timestamp_micros: 1650950229000000, + non_personalized_ads: true, + events: [ + { + name: 'view_item', + params: { + category: 'Electronics', + productID: 'ABC123', + productName: 'Example Product', + customer_name: 'Sample User', + link_imageURL: 'https://example.com/images/product.jpg', + customer_email: 'testrudder@gmail.com', + link_productURL: 'https://example.com/products/ABC123', + stockAvailability: true, + details_features_0: 'wireless charging', + details_features_1: 'water-resistant', + engagement_time_msec: 1, + transaction_currency: 'USD', + customer_loyaltyPoints: 500, + transaction_totalAmount: 150.99, + transaction_discountApplied: 20.5, + details_specifications_color: 'blue', + details_specifications_specifications_specifications_specifications_color: 'blue', + details_specifications_specifications_specifications_specifications_weight: '1.5kg', + }, + }, + ], + }, + method: 'POST', + }, + httpRes: { + data: { + validationMessages: [ + { + fieldPath: 'events.params', + description: + 'The event param [string_value: "1.5kg"] has a duplicate name [details_specifications_specifications_specifications_specifications_weight].', + validationCode: 'NAME_DUPLICATED', + }, + ], + }, + status: 200, + }, + }, +]; From 457a18b2aec03aa0dfafcadc611b5f7176e97beb Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Tue, 20 Feb 2024 22:01:24 +0530 Subject: [PATCH 25/33] fix: update proxy data type for response handler input (#3030) --- package-lock.json | 11 +- package.json | 3 +- .../networkhandler/genericNetworkHandler.js | 7 +- src/controllers/delivery.ts | 27 +- src/interfaces/DestinationService.ts | 6 +- src/services/comparator.ts | 6 +- src/services/destination/cdkV1Integration.ts | 6 +- src/services/destination/cdkV2Integration.ts | 6 +- src/services/destination/nativeIntegration.ts | 38 +- .../destination/postTransformation.ts | 24 +- src/types/index.ts | 72 ++- src/types/zodTypes.ts | 242 +++++++ .../adobe_analytics/networkHandler.js | 8 +- src/v0/destinations/am/util.test.js | 16 +- src/v0/destinations/am/utils.js | 16 +- src/v0/destinations/braze/networkHandler.js | 3 +- .../campaign_manager/networkHandler.js | 3 +- .../destinations/clevertap/networkHandler.js | 3 +- .../criteo_audience/networkHandler.js | 3 +- src/v0/destinations/fb/networkHandler.js | 3 +- src/v0/destinations/ga4/networkHandler.js | 7 +- .../networkHandler.js | 3 +- .../networkHandler.js | 3 +- .../networkHandler.js | 3 +- .../destinations/intercom/networkHandler.js | 5 +- src/v0/destinations/marketo/networkHandler.js | 5 +- .../marketo_static_list/networkHandler.js | 5 +- src/v0/destinations/pardot/networkHandler.js | 3 +- src/v0/destinations/rakuten/networkHandler.js | 5 +- .../rakuten/networkHandler.test.js | 11 +- src/v0/destinations/reddit/networkHandler.js | 3 +- .../destinations/salesforce/networkHandler.js | 5 +- .../salesforce_oauth/networkHandler.js | 5 +- .../networkHandler.js | 3 +- .../the_trade_desk/networkHandler.js | 3 +- .../destinations/tiktok_ads/networkHandler.js | 3 +- src/v0/util/facebookUtils/networkHandler.js | 3 +- .../campaign_manager/networkHandler.js | 5 +- test/integrations/common/criteo/network.ts | 72 +++ test/integrations/common/google/network.ts | 109 ++++ test/integrations/common/network.ts | 84 +++ test/integrations/component.test.ts | 6 +- .../braze/dataDelivery/business.ts | 377 +++++++++++ .../destinations/braze/dataDelivery/data.ts | 12 +- .../destinations/braze/dataDelivery/other.ts | 204 ++++++ .../destinations/braze/network.ts | 102 ++- .../campaign_manager/dataDelivery/business.ts | 606 +++++++++++++++++ .../campaign_manager/dataDelivery/data.ts | 612 +----------------- .../campaign_manager/dataDelivery/oauth.ts | 558 ++++++++++++++++ .../campaign_manager/dataDelivery/other.ts | 529 +++++++++++++++ .../destinations/campaign_manager/network.ts | 302 +++------ .../criteo_audience/dataDelivery/business.ts | 255 ++++++++ .../criteo_audience/dataDelivery/data.ts | 63 +- .../criteo_audience/dataDelivery/oauth.ts | 133 ++++ .../criteo_audience/dataDelivery/other.ts | 196 ++++++ .../destinations/criteo_audience/network.ts | 204 ++---- .../klaviyo/processor/ecomTestData.ts | 25 +- .../klaviyo/processor/groupTestData.ts | 29 +- .../klaviyo/processor/identifyTestData.ts | 50 +- .../klaviyo/processor/screenTestData.ts | 25 +- .../klaviyo/processor/trackTestData.ts | 28 +- .../klaviyo/processor/validationTestData.ts | 35 +- .../destinations/klaviyo/router/data.ts | 381 +++++------ test/integrations/destinations/mp/common.ts | 6 +- .../destinations/mp/router/data.ts | 10 + .../salesforce/dataDelivery/data.ts | 50 -- .../destinations/the_trade_desk/common.ts | 19 +- .../common.ts | 16 +- test/integrations/testTypes.ts | 84 +++ test/integrations/testUtils.ts | 187 +++++- 70 files changed, 4554 insertions(+), 1398 deletions(-) create mode 100644 src/types/zodTypes.ts create mode 100644 test/integrations/common/criteo/network.ts create mode 100644 test/integrations/common/google/network.ts create mode 100644 test/integrations/common/network.ts create mode 100644 test/integrations/destinations/braze/dataDelivery/business.ts create mode 100644 test/integrations/destinations/braze/dataDelivery/other.ts create mode 100644 test/integrations/destinations/campaign_manager/dataDelivery/business.ts create mode 100644 test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts create mode 100644 test/integrations/destinations/campaign_manager/dataDelivery/other.ts create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/business.ts create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/other.ts diff --git a/package-lock.json b/package-lock.json index d32e378d2d..54f284b2dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,8 @@ "ua-parser-js": "^1.0.37", "unset-value": "^2.0.1", "uuid": "^9.0.0", - "valid-url": "^1.0.9" + "valid-url": "^1.0.9", + "zod": "^3.22.4" }, "devDependencies": { "@commitlint/config-conventional": "^17.6.3", @@ -21108,6 +21109,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 0d8e528342..a1da6f016e 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,8 @@ "ua-parser-js": "^1.0.37", "unset-value": "^2.0.1", "uuid": "^9.0.0", - "valid-url": "^1.0.9" + "valid-url": "^1.0.9", + "zod": "^3.22.4" }, "devDependencies": { "@commitlint/config-conventional": "^17.6.3", diff --git a/src/adapters/networkhandler/genericNetworkHandler.js b/src/adapters/networkhandler/genericNetworkHandler.js index bcbcb21259..d9358085f4 100644 --- a/src/adapters/networkhandler/genericNetworkHandler.js +++ b/src/adapters/networkhandler/genericNetworkHandler.js @@ -17,13 +17,14 @@ const tags = require('../../v0/util/tags'); * will act as fall-fack for such scenarios. * */ -const responseHandler = (destinationResponse, dest) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType } = responseParams; const { status } = destinationResponse; - const message = `[Generic Response Handler] Request for destination: ${dest} Processed Successfully`; + const message = `[Generic Response Handler] Request for destination: ${destType} Processed Successfully`; // if the response from destination is not a success case build an explicit error if (!isHttpStatusSuccess(status)) { throw new NetworkError( - `[Generic Response Handler] Request failed for destination ${dest} with status: ${status}`, + `[Generic Response Handler] Request failed for destination ${destType} with status: ${status}`, status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), diff --git a/src/controllers/delivery.ts b/src/controllers/delivery.ts index eba24ccf58..4334dc33b2 100644 --- a/src/controllers/delivery.ts +++ b/src/controllers/delivery.ts @@ -1,13 +1,14 @@ /* eslint-disable prefer-destructuring */ /* eslint-disable sonarjs/no-duplicate-string */ import { Context } from 'koa'; +import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib'; import { MiscService } from '../services/misc'; import { - DeliveriesResponse, - DeliveryResponse, + DeliveryV1Response, + DeliveryV0Response, ProcessorTransformationOutput, - ProxyDeliveriesRequest, - ProxyDeliveryRequest, + ProxyV0Request, + ProxyV1Request, } from '../types/index'; import { ServiceSelector } from '../helpers/serviceSelector'; import { DeliveryTestService } from '../services/delivertTest/deliveryTest'; @@ -22,9 +23,9 @@ const NON_DETERMINABLE = 'Non-determinable'; export class DeliveryController { public static async deliverToDestination(ctx: Context) { logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body)); - let deliveryResponse: DeliveryResponse; + let deliveryResponse: DeliveryV0Response; const requestMetadata = MiscService.getRequestMetadata(ctx); - const deliveryRequest = ctx.request.body as ProxyDeliveryRequest; + const deliveryRequest = ctx.request.body as ProxyV0Request; const { destination }: { destination: string } = ctx.params; const integrationService = ServiceSelector.getNativeDestinationService(); try { @@ -33,7 +34,7 @@ export class DeliveryController { destination, requestMetadata, 'v0', - )) as DeliveryResponse; + )) as DeliveryV0Response; } catch (error: any) { const { metadata } = deliveryRequest; const metaTO = integrationService.getTags( @@ -57,9 +58,9 @@ export class DeliveryController { public static async deliverToDestinationV1(ctx: Context) { logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body)); - let deliveryResponse: DeliveriesResponse; + let deliveryResponse: DeliveryV1Response; const requestMetadata = MiscService.getRequestMetadata(ctx); - const deliveryRequest = ctx.request.body as ProxyDeliveriesRequest; + const deliveryRequest = ctx.request.body as ProxyV1Request; const { destination }: { destination: string } = ctx.params; const integrationService = ServiceSelector.getNativeDestinationService(); try { @@ -68,7 +69,7 @@ export class DeliveryController { destination, requestMetadata, 'v1', - )) as DeliveriesResponse; + )) as DeliveryV1Response; } catch (error: any) { const { metadata } = deliveryRequest; const metaTO = integrationService.getTags( @@ -84,7 +85,11 @@ export class DeliveryController { ); } ctx.body = { output: deliveryResponse }; - ControllerUtility.deliveryPostProcess(ctx, deliveryResponse.status); + if (isDefinedAndNotNullAndNotEmpty(deliveryResponse.authErrorCategory)) { + ControllerUtility.deliveryPostProcess(ctx, deliveryResponse.status); + } else { + ControllerUtility.deliveryPostProcess(ctx); + } logger.debug('Native(Delivery):: Response from transformer::', JSON.stringify(ctx.body)); return ctx; diff --git a/src/interfaces/DestinationService.ts b/src/interfaces/DestinationService.ts index bf39024d85..4947089b5d 100644 --- a/src/interfaces/DestinationService.ts +++ b/src/interfaces/DestinationService.ts @@ -1,5 +1,5 @@ import { - DeliveryResponse, + DeliveryV0Response, MetaTransferObject, ProcessorTransformationRequest, ProcessorTransformationResponse, @@ -8,7 +8,7 @@ import { UserDeletionRequest, UserDeletionResponse, ProxyRequest, - DeliveriesResponse, + DeliveryV1Response, } from '../types/index'; export interface DestinationService { @@ -49,7 +49,7 @@ export interface DestinationService { destinationType: string, requestMetadata: NonNullable, version: string, - ): Promise; + ): Promise; processUserDeletion( requests: UserDeletionRequest[], diff --git a/src/services/comparator.ts b/src/services/comparator.ts index d1e085b4bd..36cb0ebd5a 100644 --- a/src/services/comparator.ts +++ b/src/services/comparator.ts @@ -1,8 +1,8 @@ /* eslint-disable class-methods-use-this */ import { DestinationService } from '../interfaces/DestinationService'; import { - DeliveriesResponse, - DeliveryResponse, + DeliveryV0Response, + DeliveryV1Response, Destination, ErrorDetailer, MetaTransferObject, @@ -370,7 +370,7 @@ export class ComparatorService implements DestinationService { destinationType: string, requestMetadata: NonNullable, version: string, - ): Promise { + ): Promise { const primaryResplist = await this.primaryService.deliver( event, destinationType, diff --git a/src/services/destination/cdkV1Integration.ts b/src/services/destination/cdkV1Integration.ts index 197e3162ea..c6e60f5857 100644 --- a/src/services/destination/cdkV1Integration.ts +++ b/src/services/destination/cdkV1Integration.ts @@ -4,7 +4,7 @@ import path from 'path'; import { TransformationError } from '@rudderstack/integrations-lib'; import { DestinationService } from '../../interfaces/DestinationService'; import { - DeliveryResponse, + DeliveryV0Response, ErrorDetailer, MetaTransferObject, ProcessorTransformationRequest, @@ -14,7 +14,7 @@ import { UserDeletionRequest, UserDeletionResponse, ProxyRequest, - DeliveriesResponse, + DeliveryV1Response, } from '../../types/index'; import { DestinationPostTransformationService } from './postTransformation'; import tags from '../../v0/util/tags'; @@ -121,7 +121,7 @@ export class CDKV1DestinationService implements DestinationService { _event: ProxyRequest, _destinationType: string, _requestMetadata: NonNullable, - ): Promise { + ): Promise { throw new TransformationError('CDV1 Does not Implement Delivery Routine'); } diff --git a/src/services/destination/cdkV2Integration.ts b/src/services/destination/cdkV2Integration.ts index be7f0e51d5..c18a5cd936 100644 --- a/src/services/destination/cdkV2Integration.ts +++ b/src/services/destination/cdkV2Integration.ts @@ -5,7 +5,7 @@ import { TransformationError } from '@rudderstack/integrations-lib'; import { processCdkV2Workflow } from '../../cdk/v2/handler'; import { DestinationService } from '../../interfaces/DestinationService'; import { - DeliveryResponse, + DeliveryV0Response, ErrorDetailer, MetaTransferObject, ProcessorTransformationRequest, @@ -16,7 +16,7 @@ import { UserDeletionRequest, UserDeletionResponse, ProxyRequest, - DeliveriesResponse, + DeliveryV1Response, } from '../../types/index'; import tags from '../../v0/util/tags'; import { DestinationPostTransformationService } from './postTransformation'; @@ -170,7 +170,7 @@ export class CDKV2DestinationService implements DestinationService { _event: ProxyRequest, _destinationType: string, _requestMetadata: NonNullable, - ): Promise { + ): Promise { throw new TransformationError('CDKV2 Does not Implement Delivery Routine'); } diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts index 6b680e3f4a..2dd78b58e2 100644 --- a/src/services/destination/nativeIntegration.ts +++ b/src/services/destination/nativeIntegration.ts @@ -5,7 +5,7 @@ import groupBy from 'lodash/groupBy'; import cloneDeep from 'lodash/cloneDeep'; import { DestinationService } from '../../interfaces/DestinationService'; import { - DeliveryResponse, + DeliveryV0Response, ErrorDetailer, MetaTransferObject, ProcessorTransformationRequest, @@ -16,9 +16,9 @@ import { UserDeletionRequest, UserDeletionResponse, ProxyRequest, - ProxyDeliveriesRequest, - ProxyDeliveryRequest, - DeliveriesResponse, + ProxyV0Request, + ProxyV1Request, + DeliveryV1Response, DeliveryJobState, } from '../../types/index'; import { DestinationPostTransformationService } from './postTransformation'; @@ -181,7 +181,7 @@ export class NativeIntegrationDestinationService implements DestinationService { destinationType: string, _requestMetadata: NonNullable, version: string, - ): Promise { + ): Promise { try { const { networkHandler, handlerVersion } = networkHandlerFactory.getNetworkHandler( destinationType, @@ -191,24 +191,22 @@ export class NativeIntegrationDestinationService implements DestinationService { const processedProxyResponse = networkHandler.processAxiosResponse(rawProxyResponse); let rudderJobMetadata = version.toLowerCase() === 'v1' - ? (deliveryRequest as ProxyDeliveriesRequest).metadata - : (deliveryRequest as ProxyDeliveryRequest).metadata; + ? (deliveryRequest as ProxyV1Request).metadata + : (deliveryRequest as ProxyV0Request).metadata; if (version.toLowerCase() === 'v1' && handlerVersion.toLowerCase() === 'v0') { rudderJobMetadata = rudderJobMetadata[0]; } - - let responseProxy = networkHandler.responseHandler( - { - ...processedProxyResponse, - rudderJobMetadata, - }, - destinationType, - ); + const responseParams = { + destinationResponse: processedProxyResponse, + rudderJobMetadata, + destType: destinationType, + }; + let responseProxy = networkHandler.responseHandler(responseParams); // Adaption Logic for V0 to V1 if (handlerVersion.toLowerCase() === 'v0' && version.toLowerCase() === 'v1') { - const v0Response = responseProxy as DeliveryResponse; - const jobStates = (deliveryRequest as ProxyDeliveriesRequest).metadata.map( + const v0Response = responseProxy as DeliveryV0Response; + const jobStates = (deliveryRequest as ProxyV1Request).metadata.map( (metadata) => ({ error: JSON.stringify(v0Response.destinationResponse?.response), @@ -221,7 +219,7 @@ export class NativeIntegrationDestinationService implements DestinationService { status: v0Response.status, message: v0Response.message, authErrorCategory: v0Response.authErrorCategory, - } as DeliveriesResponse; + } as DeliveryV1Response; } return responseProxy; } catch (err: any) { @@ -236,10 +234,10 @@ export class NativeIntegrationDestinationService implements DestinationService { ); if (version.toLowerCase() === 'v1') { - metaTO.metadatas = (deliveryRequest as ProxyDeliveriesRequest).metadata; + metaTO.metadatas = (deliveryRequest as ProxyV1Request).metadata; return DestinationPostTransformationService.handlevV1DeliveriesFailureEvents(err, metaTO); } - metaTO.metadata = (deliveryRequest as ProxyDeliveryRequest).metadata; + metaTO.metadata = (deliveryRequest as ProxyV0Request).metadata; return DestinationPostTransformationService.handleDeliveryFailureEvents(err, metaTO); } } diff --git a/src/services/destination/postTransformation.ts b/src/services/destination/postTransformation.ts index eef4152b2b..161547683b 100644 --- a/src/services/destination/postTransformation.ts +++ b/src/services/destination/postTransformation.ts @@ -8,10 +8,10 @@ import { ProcessorTransformationResponse, RouterTransformationResponse, ProcessorTransformationOutput, - DeliveryResponse, + DeliveryV0Response, MetaTransferObject, UserDeletionResponse, - DeliveriesResponse, + DeliveryV1Response, DeliveryJobState, } from '../../types/index'; import { generateErrorObject } from '../../v0/util'; @@ -75,7 +75,13 @@ export class DestinationPostTransformationService { ): RouterTransformationResponse[] { const resultantPayloads: RouterTransformationResponse[] = cloneDeep(transformedPayloads); resultantPayloads.forEach((resultantPayload) => { - if (resultantPayload.batchedRequest && resultantPayload.batchedRequest.userId) { + if (Array.isArray(resultantPayload.batchedRequest)) { + resultantPayload.batchedRequest.forEach((batchedRequest) => { + if (batchedRequest.userId) { + batchedRequest.userId = `${batchedRequest.userId}`; + } + }); + } else if (resultantPayload.batchedRequest && resultantPayload.batchedRequest.userId) { resultantPayload.batchedRequest.userId = `${resultantPayload.batchedRequest.userId}`; } }); @@ -145,7 +151,7 @@ export class DestinationPostTransformationService { public static handleDeliveryFailureEvents( error: any, metaTo: MetaTransferObject, - ): DeliveryResponse { + ): DeliveryV0Response { const errObj = generateErrorObject(error, metaTo.errorDetails, false); const resp = { status: errObj.status, @@ -155,7 +161,7 @@ export class DestinationPostTransformationService { ...(errObj.authErrorCategory && { authErrorCategory: errObj.authErrorCategory, }), - } as DeliveryResponse; + } as DeliveryV0Response; ErrorReportingService.reportError(error, metaTo.errorContext, resp); return resp; @@ -164,7 +170,7 @@ export class DestinationPostTransformationService { public static handlevV1DeliveriesFailureEvents( error: FixMe, metaTo: MetaTransferObject, - ): DeliveriesResponse { + ): DeliveryV1Response { const errObj = generateErrorObject(error, metaTo.errorDetails, false); const metadataArray = metaTo.metadatas; if (!Array.isArray(metadataArray)) { @@ -186,10 +192,12 @@ export class DestinationPostTransformationService { const resp = { response: responses, statTags: errObj.statTags, - authErrorCategory: errObj.authErrorCategory, message: errObj.message.toString(), status: errObj.status, - } as DeliveriesResponse; + ...(errObj.authErrorCategory && { + authErrorCategory: errObj.authErrorCategory, + }), + } as DeliveryV1Response; ErrorReportingService.reportError(error, metaTo.errorContext, resp); return resp; diff --git a/src/types/index.ts b/src/types/index.ts index f4432e5c2a..68dfe3870d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -6,7 +6,7 @@ type ProcessorTransformationOutput = { type: string; method: string; endpoint: string; - userId: string; + userId?: string; headers?: Record; params?: Record; body?: { @@ -18,7 +18,7 @@ type ProcessorTransformationOutput = { files?: Record; }; -type ProxyDeliveryRequest = { +type ProxyV0Request = { version: string; type: string; method: string; @@ -33,10 +33,11 @@ type ProxyDeliveryRequest = { FORM?: Record; }; files?: Record; - metadata: Metadata; + metadata: ProxyMetdata; + destinationConfig: Record; }; -type ProxyDeliveriesRequest = { +type ProxyV1Request = { version: string; type: string; method: string; @@ -51,10 +52,24 @@ type ProxyDeliveriesRequest = { FORM?: Record; }; files?: Record; - metadata: Metadata[]; + metadata: ProxyMetdata[]; + destinationConfig: Record; }; -type ProxyRequest = ProxyDeliveryRequest | ProxyDeliveriesRequest; +type ProxyRequest = ProxyV0Request | ProxyV1Request; + +type ProxyMetdata = { + jobId: number; + attemptNum: number; + userId: string; + sourceId: string; + destinationId: string; + workspaceId: string; + secret: Record; + destInfo?: Record; + omitempty?: Record; + dontBatch: boolean; +}; type Metadata = { sourceId: string; @@ -127,7 +142,7 @@ type ProcessorTransformationRequest = { message: object; metadata: Metadata; destination: Destination; - libraries: UserTransformationLibrary[]; + libraries?: UserTransformationLibrary[]; }; type RouterTransformationRequestData = { @@ -147,17 +162,17 @@ type ProcessorTransformationResponse = { metadata: Metadata; statusCode: number; error?: string; - statTags: object; + statTags?: object; }; type RouterTransformationResponse = { - batchedRequest?: ProcessorTransformationOutput; + batchedRequest?: ProcessorTransformationOutput | ProcessorTransformationOutput[]; metadata: Metadata[]; destination: Destination; batched: boolean; statusCode: number; - error: string; - statTags: object; + error?: string; + statTags?: object; }; type SourceTransformationOutput = { @@ -172,7 +187,7 @@ type SourceTransformationResponse = { statTags: object; }; -type DeliveryResponse = { +type DeliveryV0Response = { status: number; message: string; destinationResponse: any; @@ -183,13 +198,14 @@ type DeliveryResponse = { type DeliveryJobState = { error: string; statusCode: number; - metadata: Metadata; + metadata: ProxyMetdata; }; -type DeliveriesResponse = { - status?: number; - message?: string; +type DeliveryV1Response = { + status: number; + message: string; statTags?: object; + destinationResponse?: any; authErrorCategory?: string; response: DeliveryJobState[]; }; @@ -236,13 +252,22 @@ type ErrorDetailer = { sourceId?: string; }; -type MetaTransferObject = { - metadatas?: Metadata[]; - metadata?: Metadata; +type MetaTransferObjectForProxy = { + metadata?: ProxyMetdata; + metadatas?: ProxyMetdata[]; errorDetails: ErrorDetailer; errorContext: string; }; +type MetaTransferObject = + | { + metadatas?: Metadata[]; + metadata?: Metadata; + errorDetails: ErrorDetailer; + errorContext: string; + } + | MetaTransferObjectForProxy; + type UserTransformationResponse = { transformedEvent: RudderMessage; metadata: Metadata; @@ -307,8 +332,8 @@ type SourceInput = { export { ComparatorInput, DeliveryJobState, - DeliveryResponse, - DeliveriesResponse, + DeliveryV0Response, + DeliveryV1Response, Destination, ErrorDetailer, MessageIdMetadataMap, @@ -317,9 +342,10 @@ export { ProcessorTransformationOutput, ProcessorTransformationRequest, ProcessorTransformationResponse, - ProxyDeliveriesRequest, - ProxyDeliveryRequest, + ProxyMetdata, ProxyRequest, + ProxyV0Request, + ProxyV1Request, RouterTransformationRequest, RouterTransformationRequestData, RouterTransformationResponse, diff --git a/src/types/zodTypes.ts b/src/types/zodTypes.ts new file mode 100644 index 0000000000..0a65a2bae2 --- /dev/null +++ b/src/types/zodTypes.ts @@ -0,0 +1,242 @@ +import { z } from 'zod'; +import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib'; +import { isHttpStatusSuccess } from '../v0/util'; + +const ProcessorTransformationOutputSchema = z.object({ + version: z.string(), + type: z.string(), + method: z.string(), + endpoint: z.string(), + userId: z.string().optional(), + headers: z.record(z.unknown()).optional(), + params: z.record(z.unknown()).optional(), + body: z + .object({ + JSON: z.record(z.unknown()).optional(), + JSON_ARRAY: z.record(z.unknown()).optional(), + XML: z.record(z.unknown()).optional(), + FORM: z.record(z.unknown()).optional(), + }) + .optional(), + files: z.record(z.unknown()).optional(), +}); + +export const ProcessorTransformationResponseSchema = z + .object({ + output: ProcessorTransformationOutputSchema.optional(), + metadata: z.record(z.unknown()), + statusCode: z.number(), + error: z.string().optional(), + statTags: z.record(z.unknown()).optional(), + }) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.statusCode)) { + return ( + isDefinedAndNotNullAndNotEmpty(data.statTags) || + isDefinedAndNotNullAndNotEmpty(data.error) + ); + } + return true; + }, + { + message: "statTags and error can't be empty when status is not a 2XX", + path: ['statTags', 'error'], // Pointing out which field is invalid + }, + ) + .refine( + (data) => { + if (isHttpStatusSuccess(data.statusCode)) { + return isDefinedAndNotNullAndNotEmpty(data.output); + } + return true; + }, + { + message: "output can't be empty when status is 2XX", + path: ['output'], // Pointing out which field is invalid + }, + ); + +export const ProcessorTransformationResponseListSchema = z.array( + ProcessorTransformationResponseSchema, +); + +export const RouterTransformationResponseSchema = z + .object({ + batchedRequest: z + .array(ProcessorTransformationOutputSchema) + .or(ProcessorTransformationOutputSchema) + .optional(), + metadata: z.array(z.record(z.unknown())), // array of metadata + destination: z.record(z.unknown()), + batched: z.boolean(), + statusCode: z.number(), + error: z.string().optional(), + statTags: z.record(z.unknown()).optional(), + }) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.statusCode)) { + return ( + isDefinedAndNotNullAndNotEmpty(data.statTags) || + isDefinedAndNotNullAndNotEmpty(data.error) + ); + } + return true; + }, + { + message: "statTags and error can't be empty when status is not a 2XX", + path: ['statTags', 'error'], // Pointing out which field is invalid + }, + ) + .refine( + (data) => { + if (isHttpStatusSuccess(data.statusCode)) { + return isDefinedAndNotNullAndNotEmpty(data.batchedRequest); + } + return true; + }, + { + message: "batchedRequest can't be empty when status is 2XX", + path: ['batchedRequest'], // Pointing out which field is invalid + }, + ); + +export const RouterTransformationResponseListSchema = z.array(RouterTransformationResponseSchema); + +// Proxy related schemas +export const ProxyMetadataSchema = z.object({ + jobId: z.number(), + attemptNum: z.number(), + userId: z.string(), + sourceId: z.string(), + destinationId: z.string(), + workspaceId: z.string(), + secret: z.record(z.unknown()), + destInfo: z.record(z.unknown()).optional(), + omitempty: z.record(z.unknown()).optional(), + dontBatch: z.boolean(), +}); + +export const ProxyV0RequestSchema = z.object({ + version: z.string(), + type: z.string(), + method: z.string(), + endpoint: z.string(), + userId: z.string(), + headers: z.record(z.unknown()).optional(), + params: z.record(z.unknown()).optional(), + body: z + .object({ + JSON: z.record(z.unknown()).optional(), + JSON_ARRAY: z.record(z.unknown()).optional(), + XML: z.record(z.unknown()).optional(), + FORM: z.record(z.unknown()).optional(), + }) + .optional(), + files: z.record(z.unknown()).optional(), + metadata: ProxyMetadataSchema, + destinationConfig: z.record(z.unknown()), +}); + +export const ProxyV1RequestSchema = z.object({ + version: z.string(), + type: z.string(), + method: z.string(), + endpoint: z.string(), + userId: z.string(), + headers: z.record(z.unknown()).optional(), + params: z.record(z.unknown()).optional(), + body: z + .object({ + JSON: z.record(z.unknown()).optional(), + JSON_ARRAY: z.record(z.unknown()).optional(), + XML: z.record(z.unknown()).optional(), + FORM: z.record(z.unknown()).optional(), + }) + .optional(), + files: z.record(z.unknown()).optional(), + metadata: z.array(ProxyMetadataSchema), + destinationConfig: z.record(z.unknown()), +}); + +const validateStatTags = (data: any) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.statTags); + } + return true; +}; + +const validateAuthErrorCategory = (data: any) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.authErrorCategory); + } + return true; +}; + +export const DeliveryV0ResponseSchema = z + .object({ + status: z.number(), + message: z.string(), + destinationResponse: z.unknown(), + statTags: z.record(z.unknown()).optional(), + authErrorCategory: z.string().optional(), + }) + .refine(validateStatTags, { + // eslint-disable-next-line sonarjs/no-duplicate-string + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }); + +export const DeliveryV0ResponseSchemaForOauth = z + .object({ + status: z.number(), + message: z.string(), + destinationResponse: z.unknown(), + statTags: z.record(z.unknown()).optional(), + authErrorCategory: z.string().optional(), + }) + .refine(validateStatTags, { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }) + .refine(validateAuthErrorCategory, { + message: "authErrorCategory can't be empty when status is not a 2XX", + path: ['authErrorCategory'], // Pointing out which field is invalid + }); + +const DeliveryJobStateSchema = z.object({ + error: z.string(), + statusCode: z.number(), + metadata: ProxyMetadataSchema, +}); + +export const DeliveryV1ResponseSchema = z + .object({ + status: z.number(), + message: z.string(), + statTags: z.record(z.unknown()).optional(), + authErrorCategory: z.string().optional(), + response: z.array(DeliveryJobStateSchema), + }) + .refine(validateStatTags, { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }); + +export const DeliveryV1ResponseSchemaForOauth = z + .object({ + status: z.number(), + message: z.string(), + statTags: z.record(z.unknown()).optional(), + authErrorCategory: z.string().optional(), + response: z.array(DeliveryJobStateSchema), + }) + .refine(validateStatTags, { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }) + .refine(validateAuthErrorCategory, { + message: "authErrorCategory can't be empty when status is not a 2XX", + path: ['authErrorCategory'], // Pointing out which field is invalid + }); diff --git a/src/v0/destinations/adobe_analytics/networkHandler.js b/src/v0/destinations/adobe_analytics/networkHandler.js index 0ec1fad286..8715721f85 100644 --- a/src/v0/destinations/adobe_analytics/networkHandler.js +++ b/src/v0/destinations/adobe_analytics/networkHandler.js @@ -15,7 +15,9 @@ function extractContent(xmlPayload, tagName) { return match ? match[1] : null; } -const responseHandler = (destinationResponse, dest) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType } = responseParams; + const message = `[${DESTINATION}] - Request Processed Successfully`; const { response, status } = destinationResponse; @@ -27,11 +29,11 @@ const responseHandler = (destinationResponse, dest) => { if (responseStatus === 'FAILURE') { if (reason) { throw new InstrumentationError( - `[${DESTINATION} Response Handler] Request failed for destination ${dest} : ${reason}`, + `[${DESTINATION} Response Handler] Request failed for destination ${destType} : ${reason}`, ); } else { throw new InstrumentationError( - `[${DESTINATION} Response Handler] Request failed for destination ${dest} with a general error`, + `[${DESTINATION} Response Handler] Request failed for destination ${destType} with a general error`, ); } } diff --git a/src/v0/destinations/am/util.test.js b/src/v0/destinations/am/util.test.js index 455f9117ef..fa30a74757 100644 --- a/src/v0/destinations/am/util.test.js +++ b/src/v0/destinations/am/util.test.js @@ -65,9 +65,7 @@ describe('getUnsetObj', () => { }); }); - describe('validateEventType', () => { - it('should validate event type when it is valid with only page name given', () => { expect(() => { validateEventType('Home Page'); @@ -77,21 +75,25 @@ describe('validateEventType', () => { it('should throw an error when event type is null', () => { expect(() => { validateEventType(null); - }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }).toThrow( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); }); it('should throw an error when event type is undefined', () => { expect(() => { validateEventType(undefined); - }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }).toThrow( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); }); // Function receives an empty string as event type it('should throw an error when event type is an empty string', () => { expect(() => { validateEventType(''); - }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }).toThrow( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); }); - }); - diff --git a/src/v0/destinations/am/utils.js b/src/v0/destinations/am/utils.js index e51d09aaa7..ed1b772fca 100644 --- a/src/v0/destinations/am/utils.js +++ b/src/v0/destinations/am/utils.js @@ -111,16 +111,16 @@ const getUnsetObj = (message) => { }; /** - * Check for evType as in some cases, like when the page name is absent, + * Check for evType as in some cases, like when the page name is absent, * either the template depends only on the event.name or there is no template provided by user - * @param {*} evType + * @param {*} evType */ const validateEventType = (evType) => { - if (!isDefinedAndNotNull(evType) || (typeof evType === "string" && evType.length ===0)) { - throw new InstrumentationError( - 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', - ); - } + if (!isDefinedAndNotNull(evType) || (typeof evType === 'string' && evType.length === 0)) { + throw new InstrumentationError( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); + } }; module.exports = { getOSName, @@ -131,5 +131,5 @@ module.exports = { getBrand, getEventId, getUnsetObj, - validateEventType + validateEventType, }; diff --git a/src/v0/destinations/braze/networkHandler.js b/src/v0/destinations/braze/networkHandler.js index c6cf7222ea..b1363419b3 100644 --- a/src/v0/destinations/braze/networkHandler.js +++ b/src/v0/destinations/braze/networkHandler.js @@ -11,7 +11,8 @@ const tags = require('../../util/tags'); const stats = require('../../../util/stats'); // eslint-disable-next-line @typescript-eslint/no-unused-vars -const responseHandler = (destinationResponse, _dest) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request for ${DESTINATION} Processed Successfully`; const { response, status } = destinationResponse; // if the response from destination is not a success case build an explicit error diff --git a/src/v0/destinations/campaign_manager/networkHandler.js b/src/v0/destinations/campaign_manager/networkHandler.js index a1fa24835c..df13b72adc 100644 --- a/src/v0/destinations/campaign_manager/networkHandler.js +++ b/src/v0/destinations/campaign_manager/networkHandler.js @@ -44,7 +44,8 @@ function checkIfFailuresAreRetryable(response) { } } -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully`; const { response, status } = destinationResponse; diff --git a/src/v0/destinations/clevertap/networkHandler.js b/src/v0/destinations/clevertap/networkHandler.js index e17afb57d1..02b523f3fc 100644 --- a/src/v0/destinations/clevertap/networkHandler.js +++ b/src/v0/destinations/clevertap/networkHandler.js @@ -7,7 +7,8 @@ const { } = require('../../../adapters/utils/networkUtils'); const tags = require('../../util/tags'); -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = 'Request Processed Successfully'; const { response, status } = destinationResponse; diff --git a/src/v0/destinations/criteo_audience/networkHandler.js b/src/v0/destinations/criteo_audience/networkHandler.js index 18bd9a93a0..6032aabcdd 100644 --- a/src/v0/destinations/criteo_audience/networkHandler.js +++ b/src/v0/destinations/criteo_audience/networkHandler.js @@ -67,7 +67,8 @@ const criteoAudienceRespHandler = (destResponse, stageMsg) => { ); }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request Processed Successfully`; const { status } = destinationResponse; if (!isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/fb/networkHandler.js b/src/v0/destinations/fb/networkHandler.js index 06235fab40..7ba5b88adc 100644 --- a/src/v0/destinations/fb/networkHandler.js +++ b/src/v0/destinations/fb/networkHandler.js @@ -2,7 +2,8 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils') const { errorResponseHandler } = require('../facebook_pixel/networkHandler'); const { prepareProxyRequest, proxyRequest } = require('../../../adapters/network'); -const destResponseHandler = (destinationResponse) => { +const destResponseHandler = (responseParams) => { + const { destinationResponse } = responseParams; errorResponseHandler(destinationResponse); return { destinationResponse: destinationResponse.response, diff --git a/src/v0/destinations/ga4/networkHandler.js b/src/v0/destinations/ga4/networkHandler.js index 2cb98e1460..da8ae2ea30 100644 --- a/src/v0/destinations/ga4/networkHandler.js +++ b/src/v0/destinations/ga4/networkHandler.js @@ -8,7 +8,8 @@ const { isDefinedAndNotNull, isDefined, isHttpStatusSuccess } = require('../../u const tags = require('../../util/tags'); -const responseHandler = (destinationResponse, dest) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType } = responseParams; const message = `[GA4 Response Handler] - Request Processed Successfully`; let { status } = destinationResponse; const { response } = destinationResponse; @@ -29,7 +30,7 @@ const responseHandler = (destinationResponse, dest) => { // Build the error in case the validationMessages[] is non-empty const { description, validationCode, fieldPath } = response.validationMessages[0]; throw new NetworkError( - `Validation Server Response Handler:: Validation Error for ${dest} of field path :${fieldPath} | ${validationCode}-${description}`, + `Validation Server Response Handler:: Validation Error for ${destType} of field path :${fieldPath} | ${validationCode}-${description}`, 400, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(400), @@ -42,7 +43,7 @@ const responseHandler = (destinationResponse, dest) => { // if the response from destination is not a success case build an explicit error if (!isHttpStatusSuccess(status)) { throw new NetworkError( - `[GA4 Response Handler] Request failed for destination ${dest} with status: ${status}`, + `[GA4 Response Handler] Request failed for destination ${destType} with status: ${status}`, status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js index 7266154a09..b4590fb71c 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js @@ -102,7 +102,8 @@ const ProxyRequest = async (request) => { return response; }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = 'Request Processed Successfully'; const { status } = destinationResponse; if (isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js index 6922cde8c8..318b7802df 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js @@ -251,7 +251,8 @@ const ProxyRequest = async (request) => { return response; }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `[Google Ads Offline Conversions Response Handler] - Request processed successfully`; const { status } = destinationResponse; if (isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js index bf703ccb1b..dbd055f1a1 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js @@ -153,7 +153,8 @@ const gaAudienceRespHandler = (destResponse, stageMsg) => { ); }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request Processed Successfully`; const { status, response } = destinationResponse; if (isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/intercom/networkHandler.js b/src/v0/destinations/intercom/networkHandler.js index a4106257b3..8485dac52e 100644 --- a/src/v0/destinations/intercom/networkHandler.js +++ b/src/v0/destinations/intercom/networkHandler.js @@ -13,8 +13,9 @@ const errorResponseHandler = (destinationResponse, dest) => { } }; -const destResponseHandler = (destinationResponse, dest) => { - errorResponseHandler(destinationResponse, dest); +const destResponseHandler = (responseParams) => { + const { destinationResponse, destType } = responseParams; + errorResponseHandler(destinationResponse, destType); return { destinationResponse: destinationResponse.response, message: 'Request Processed Successfully', diff --git a/src/v0/destinations/marketo/networkHandler.js b/src/v0/destinations/marketo/networkHandler.js index 7abcc65c02..1d4b316e8d 100644 --- a/src/v0/destinations/marketo/networkHandler.js +++ b/src/v0/destinations/marketo/networkHandler.js @@ -3,9 +3,10 @@ const { marketoResponseHandler } = require('./util'); const { proxyRequest, prepareProxyRequest } = require('../../../adapters/network'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); -const responseHandler = (destinationResponse, destType) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType,rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; - const { status, rudderJobMetadata } = destinationResponse; + const { status } = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); // check for marketo application level failures marketoResponseHandler( diff --git a/src/v0/destinations/marketo_static_list/networkHandler.js b/src/v0/destinations/marketo_static_list/networkHandler.js index 30b053b9d3..9e73cd1f91 100644 --- a/src/v0/destinations/marketo_static_list/networkHandler.js +++ b/src/v0/destinations/marketo_static_list/networkHandler.js @@ -4,9 +4,10 @@ const v0Utils = require('../../util'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const { DESTINATION } = require('./config'); -const responseHandler = (destinationResponse, destType) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; - const { status, rudderJobMetadata } = destinationResponse; + const { status} = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); // check for marketo application level failures marketoResponseHandler( diff --git a/src/v0/destinations/pardot/networkHandler.js b/src/v0/destinations/pardot/networkHandler.js index 12b4abbc53..edf713ce97 100644 --- a/src/v0/destinations/pardot/networkHandler.js +++ b/src/v0/destinations/pardot/networkHandler.js @@ -65,7 +65,8 @@ const pardotRespHandler = (destResponse, stageMsg) => { ); }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = 'Request Processed Successfully'; const { status } = destinationResponse; // else successfully return status, message and original destination response diff --git a/src/v0/destinations/rakuten/networkHandler.js b/src/v0/destinations/rakuten/networkHandler.js index 1b16bd5538..6c89d83947 100644 --- a/src/v0/destinations/rakuten/networkHandler.js +++ b/src/v0/destinations/rakuten/networkHandler.js @@ -27,7 +27,8 @@ const extractContent = (xmlPayload, tagName) => { return match ? match[1] : null; }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const msg = `[${DESTINATION} Response Handler] - Request Processed Successfully`; const { response, status } = destinationResponse; if (status === 400) { @@ -99,5 +100,5 @@ class networkHandler { module.exports = { networkHandler, - responseHandler + responseHandler, }; diff --git a/src/v0/destinations/rakuten/networkHandler.test.js b/src/v0/destinations/rakuten/networkHandler.test.js index 70461c86c1..da74e05cb3 100644 --- a/src/v0/destinations/rakuten/networkHandler.test.js +++ b/src/v0/destinations/rakuten/networkHandler.test.js @@ -8,7 +8,7 @@ describe('responseHandler', () => { status: 200, }; - const result = responseHandler(destinationResponse); + const result = responseHandler({ destinationResponse }); expect(result.status).toBe(200); expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully'); @@ -21,7 +21,7 @@ describe('responseHandler', () => { status: 400, }; expect(() => { - responseHandler(destinationResponse); + responseHandler({ destinationResponse }); }).toThrow('Request failed with status: 400 due to invalid Marketing Id'); }); @@ -31,7 +31,7 @@ describe('responseHandler', () => { status: 200, }; expect(() => { - responseHandler(destinationResponse); + responseHandler({ destinationResponse }); }).toThrow( 'Request failed with status: 200 due to Access denied. Can you try to enable pixel tracking for this mid.', ); @@ -43,7 +43,7 @@ describe('responseHandler', () => { status: 200, }; - const result = responseHandler(destinationResponse); + const result = responseHandler({ destinationResponse }); expect(result.status).toBe(200); expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully'); @@ -57,8 +57,7 @@ describe('responseHandler', () => { }; expect(() => { - responseHandler(destinationResponse); + responseHandler({ destinationResponse }); }).toThrow('Request failed with status: 200 with number of bad records 1'); - }); }); diff --git a/src/v0/destinations/reddit/networkHandler.js b/src/v0/destinations/reddit/networkHandler.js index 836c015859..55087b52ac 100644 --- a/src/v0/destinations/reddit/networkHandler.js +++ b/src/v0/destinations/reddit/networkHandler.js @@ -18,7 +18,8 @@ const redditRespHandler = (destResponse) => { ); } }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request Processed Successfully`; const { status } = destinationResponse; if (!isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/salesforce/networkHandler.js b/src/v0/destinations/salesforce/networkHandler.js index 918084cc89..ac31241775 100644 --- a/src/v0/destinations/salesforce/networkHandler.js +++ b/src/v0/destinations/salesforce/networkHandler.js @@ -3,13 +3,14 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils') const { LEGACY } = require('./config'); const { salesforceResponseHandler } = require('./utils'); -const responseHandler = (destinationResponse, destType) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = `Request for destination: ${destType} Processed Successfully`; salesforceResponseHandler( destinationResponse, 'during Salesforce Response Handling', - destinationResponse?.rudderJobMetadata?.destInfo?.authKey, + rudderJobMetadata?.destInfo?.authKey, LEGACY, ); diff --git a/src/v0/destinations/salesforce_oauth/networkHandler.js b/src/v0/destinations/salesforce_oauth/networkHandler.js index 2bcace31c9..b6cbed77f9 100644 --- a/src/v0/destinations/salesforce_oauth/networkHandler.js +++ b/src/v0/destinations/salesforce_oauth/networkHandler.js @@ -3,13 +3,14 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils') const { OAUTH } = require('../salesforce/config'); const { salesforceResponseHandler } = require('../salesforce/utils'); -const responseHandler = (destinationResponse, destType) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = `Request for destination: ${destType} Processed Successfully`; salesforceResponseHandler( destinationResponse, 'during Salesforce Response Handling', - destinationResponse?.rudderJobMetadata?.destInfo?.authKey, + rudderJobMetadata?.destInfo?.authKey, OAUTH, ); diff --git a/src/v0/destinations/snapchat_custom_audience/networkHandler.js b/src/v0/destinations/snapchat_custom_audience/networkHandler.js index db36f6f518..feedaea3e3 100644 --- a/src/v0/destinations/snapchat_custom_audience/networkHandler.js +++ b/src/v0/destinations/snapchat_custom_audience/networkHandler.js @@ -80,7 +80,8 @@ const scaAudienceRespHandler = (destResponse, stageMsg) => { ); }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request Processed Successfully`; const { status } = destinationResponse; if (isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/the_trade_desk/networkHandler.js b/src/v0/destinations/the_trade_desk/networkHandler.js index 30378e5ace..4d9e5321e7 100644 --- a/src/v0/destinations/the_trade_desk/networkHandler.js +++ b/src/v0/destinations/the_trade_desk/networkHandler.js @@ -41,7 +41,8 @@ const proxyRequest = async (request) => { return response; }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = 'Request Processed Successfully'; const { response, status } = destinationResponse; diff --git a/src/v0/destinations/tiktok_ads/networkHandler.js b/src/v0/destinations/tiktok_ads/networkHandler.js index ae93b1ec15..5d4b7fd4e0 100644 --- a/src/v0/destinations/tiktok_ads/networkHandler.js +++ b/src/v0/destinations/tiktok_ads/networkHandler.js @@ -8,7 +8,8 @@ const { DESTINATION } = require('./config'); const { TAG_NAMES } = require('../../util/tags'); const { HTTP_STATUS_CODES } = require('../../util/constant'); -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const msg = `[${DESTINATION} Response Handler] - Request Processed Successfully`; const { response: { code }, diff --git a/src/v0/util/facebookUtils/networkHandler.js b/src/v0/util/facebookUtils/networkHandler.js index 7219120dd7..d5a731067f 100644 --- a/src/v0/util/facebookUtils/networkHandler.js +++ b/src/v0/util/facebookUtils/networkHandler.js @@ -275,7 +275,8 @@ const errorResponseHandler = (destResponse) => { ); }; -const destResponseHandler = (destinationResponse) => { +const destResponseHandler = (responseParams) => { + const { destinationResponse } = responseParams; errorResponseHandler(destinationResponse); return { destinationResponse: destinationResponse.response, diff --git a/src/v1/destinations/campaign_manager/networkHandler.js b/src/v1/destinations/campaign_manager/networkHandler.js index 431cbd6966..79f7e7f93b 100644 --- a/src/v1/destinations/campaign_manager/networkHandler.js +++ b/src/v1/destinations/campaign_manager/networkHandler.js @@ -34,10 +34,11 @@ function isEventAbortableAndExtractErrMsg(element, proxyOutputObj) { return isAbortable; } -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse, rudderJobMetadata } = responseParams; const message = `[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully`; const responseWithIndividualEvents = []; - const { response, status, rudderJobMetadata } = destinationResponse; + const { response, status } = destinationResponse; if (isHttpStatusSuccess(status)) { // check for Partial Event failures and Successes diff --git a/test/integrations/common/criteo/network.ts b/test/integrations/common/criteo/network.ts new file mode 100644 index 0000000000..cd5e1ca1e8 --- /dev/null +++ b/test/integrations/common/criteo/network.ts @@ -0,0 +1,72 @@ +const headers = { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', +}; +const params = { destination: 'criteo_audience' }; +const method = 'PATCH'; +const commonData = { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, +}; + +export const networkCallsData = [ + { + description: 'Mock response depicting expired access token error', + httpReq: { + url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken', + data: commonData, + params, + headers, + method, + }, + httpRes: { + code: '400', + data: { + errors: [ + { + traceIdentifier: '80a1a0ba3981b04da847d05700752c77', + type: 'authorization', + code: 'authorization-token-expired', + instance: '/2022-10/audiences/123/contactlist', + title: 'The authorization token has expired', + }, + ], + }, + status: 401, + }, + }, + { + description: 'Mock response depicting invalid access token error', + httpReq: { + url: 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken', + data: commonData, + params, + headers, + method, + }, + httpRes: { + code: '400', + data: { + errors: [ + { + traceIdentifier: '80a1a0ba3981b04da847d05700752c77', + type: 'authorization', + code: 'authorization-token-invalid', + instance: '/2022-10/audiences/123/contactlist', + title: 'The authorization header is invalid', + }, + ], + }, + status: 401, + }, + }, +]; diff --git a/test/integrations/common/google/network.ts b/test/integrations/common/google/network.ts new file mode 100644 index 0000000000..95b76f8da8 --- /dev/null +++ b/test/integrations/common/google/network.ts @@ -0,0 +1,109 @@ +// Ads API +// Ref: https://developers.google.com/google-ads/api/docs/get-started/common-errors + +export const networkCallsData = [ + { + description: 'Mock response depicting CREDENTIALS_MISSING error', + httpReq: { + method: 'post', + url: 'https://googleapis.com/test_url_for_credentials_missing', + }, + httpRes: { + data: { + error: { + code: 401, + message: + 'Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.', + errors: [ + { + message: 'Login Required.', + domain: 'global', + reason: 'required', + location: 'Authorization', + locationType: 'header', + }, + ], + status: 'UNAUTHENTICATED', + details: [ + { + '@type': 'type.googleapis.com/google.rpc.ErrorInfo', + reason: 'CREDENTIALS_MISSING', + domain: 'googleapis.com', + metadata: { + method: 'google.ads.xfa.op.v4.DfareportingConversions.Batchinsert', + service: 'googleapis.com', + }, + }, + ], + }, + }, + status: 401, + }, + }, + { + description: 'Mock response depicting ACCESS_TOKEN_SCOPE_INSUFFICIENT error', + httpReq: { + method: 'post', + url: 'https://googleapis.com/test_url_for_access_token_scope_insufficient', + }, + httpRes: { + data: { + error: { + code: 403, + message: 'Request had insufficient authentication scopes.', + errors: [ + { + message: 'Insufficient Permission', + domain: 'global', + reason: 'insufficientPermissions', + }, + ], + status: 'PERMISSION_DENIED', + details: [ + { + '@type': 'type.googleapis.com/google.rpc.ErrorInfo', + reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT', + domain: 'googleapis.com', + metadata: { + service: 'gmail.googleapis.com', + method: 'caribou.api.proto.MailboxService.GetProfile', + }, + }, + ], + }, + }, + status: 403, + }, + }, + { + description: 'Mock response for google.auth.exceptions.RefreshError invalid_grant error', + httpReq: { + method: 'post', + url: 'https://googleapis.com/test_url_for_invalid_grant', + }, + httpRes: { + data: { + error: { + code: 403, + message: 'invalid_grant', + error_description: 'Bad accesss', + }, + }, + status: 403, + }, + }, + { + description: 'Mock response for google.auth.exceptions.RefreshError refresh_token error', + httpReq: { + method: 'post', + url: 'https://googleapis.com/test_url_for_refresh_error', + }, + httpRes: { + data: { + error: 'unauthorized', + error_description: 'Access token expired: 2020-10-20T12:00:00.000Z', + }, + status: 401, + }, + }, +]; diff --git a/test/integrations/common/network.ts b/test/integrations/common/network.ts new file mode 100644 index 0000000000..8b0ed16c72 --- /dev/null +++ b/test/integrations/common/network.ts @@ -0,0 +1,84 @@ +export const networkCallsData = [ + { + description: 'Mock response depicting SERVICE NOT AVAILABLE error', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_service_not_available', + }, + httpRes: { + data: { + error: { + message: 'Service Unavailable', + description: + 'The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later.', + }, + }, + status: 503, + }, + }, + { + description: 'Mock response depicting INTERNAL SERVER ERROR error with post method', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_internal_server_error', + }, + httpRes: { + data: 'Internal Server Error', + status: 500, + }, + }, + { + description: 'Mock response depicting INTERNAL SERVER ERROR error with patch method', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_internal_server_error', + }, + httpRes: { + data: 'Internal Server Error', + status: 500, + }, + }, + { + description: 'Mock response depicting GATEWAY TIME OUT error', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_gateway_time_out', + }, + httpRes: { + data: 'Gateway Timeout', + status: 504, + }, + }, + { + description: 'Mock response depicting null response', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_null_response', + }, + httpRes: { + data: null, + status: 500, + }, + }, + { + description: 'Mock response depicting null and no status', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_null_and_no_status', + }, + httpRes: { + data: null, + }, + }, + { + description: 'Mock response depicting TOO MANY REQUESTS error with patch method', + httpReq: { + method: 'patch', + url: 'https://random_test_url/test_for_too_many_requests', + }, + httpRes: { + data: {}, + status: 429, + }, + }, +]; diff --git a/test/integrations/component.test.ts b/test/integrations/component.test.ts index ec4fb02dc1..388c283c61 100644 --- a/test/integrations/component.test.ts +++ b/test/integrations/component.test.ts @@ -16,6 +16,7 @@ import { getMockHttpCallsData, getAllTestMockDataFilePaths, addMock, + validateTestWithZOD, } from './testUtils'; import tags from '../../src/v0/util/tags'; import { Server } from 'http'; @@ -53,7 +54,7 @@ if (opts.generate === 'true') { let server: Server; -const REPORT_COMPATIBLE_INTEGRATION = ['klaviyo']; +const INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE = ['klaviyo', 'campaign_manager', 'criteo_audience']; beforeAll(async () => { initaliseReport(); @@ -147,7 +148,8 @@ const testRoute = async (route, tcData: TestCaseData) => { expect(response.status).toEqual(outputResp.status); - if (REPORT_COMPATIBLE_INTEGRATION.includes(tcData.name?.toLocaleLowerCase())) { + if (INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE.includes(tcData.name?.toLocaleLowerCase())) { + expect(validateTestWithZOD(tcData, response)).toEqual(true); const bodyMatched = _.isEqual(response.body, outputResp.body); const statusMatched = response.status === outputResp.status; if (bodyMatched && statusMatched) { diff --git a/test/integrations/destinations/braze/dataDelivery/business.ts b/test/integrations/destinations/braze/dataDelivery/business.ts new file mode 100644 index 0000000000..4997c5ffae --- /dev/null +++ b/test/integrations/destinations/braze/dataDelivery/business.ts @@ -0,0 +1,377 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +const BRAZE_USERS_TRACK_ENDPOINT = 'https://rest.iad-03.braze.com/users/track'; + +const partner = 'RudderStack'; + +const headers = { + Accept: 'application/json', + Authorization: 'Bearer api_key', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', +}; + +const BrazeEvent1 = { + name: 'Product List Viewed', + time: '2023-11-30T21:48:45.634Z', + properties: { + products: [ + { + sku: '23-04-52-62-01-18', + name: 'Broman Hoodie', + price: '97.99', + variant: [ + { + id: 39653520310368, + sku: '23-04-52-62-01-18', + grams: 0, + price: '97.99', + title: '(SM)', + weight: 0, + option1: '(SM)', + taxable: true, + position: 1, + tax_code: '', + created_at: '2023-05-18T12:56:22-06:00', + product_id: 6660780884064, + updated_at: '2023-11-30T15:48:43-06:00', + weight_unit: 'kg', + quantity_rule: { + min: 1, + increment: 1, + }, + compare_at_price: '139.99', + inventory_policy: 'deny', + requires_shipping: true, + inventory_quantity: 8, + fulfillment_service: 'manual', + inventory_management: 'shopify', + quantity_price_breaks: [], + old_inventory_quantity: 8, + }, + ], + category: '62 OTHER/RETRO', + currency: 'CAD', + product_id: 6660780884064, + }, + { + sku: '23-04-08-61-01-18', + name: 'Kipling Camo Hoodie', + price: '69.99', + variant: [ + { + id: 39672628740192, + sku: '23-04-08-61-01-18', + grams: 0, + price: '69.99', + title: '(SM)', + weight: 0, + option1: '(SM)', + taxable: true, + position: 1, + tax_code: '', + created_at: '2023-06-28T12:52:56-06:00', + product_id: 6666835853408, + updated_at: '2023-11-30T15:48:43-06:00', + weight_unit: 'kg', + quantity_rule: { + min: 1, + increment: 1, + }, + compare_at_price: '99.99', + inventory_policy: 'deny', + requires_shipping: true, + inventory_quantity: 8, + fulfillment_service: 'manual', + inventory_management: 'shopify', + quantity_price_breaks: [], + old_inventory_quantity: 8, + }, + ], + category: 'Misc', + currency: 'CAD', + product_id: 6666835853408, + }, + ], + }, + _update_existing_only: false, + user_alias: { + alias_name: 'ab7de609-9bec-8e1c-42cd-084a1cd93a4e', + alias_label: 'rudder_id', + }, +}; + +const BrazeEvent2 = { + name: 'Add to Cart', + time: '2020-01-24T11:59:02.403+05:30', + properties: { + revenue: 50, + }, + external_id: 'mickeyMouse', +}; + +const BrazePurchaseEvent = { + product_id: '507f1f77bcf86cd799439011', + price: 0, + currency: 'USD', + quantity: 1, + time: '2020-01-24T11:59:02.402+05:30', + _update_existing_only: false, + user_alias: { + alias_name: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca', + alias_label: 'rudder_id', + }, +}; + +const metadataArray = [generateMetadata(1), generateMetadata(2), generateMetadata(3)]; + +const errorMessages = { + message_1: '{"events_processed":2,"purchases_processed":1,"message":"success"}', + message_2: + '{"events_processed":1,"message":"success","errors":[{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":1},{"type":"\'quantity\' is not valid","input_array":"purchases","index":0}]}', + message_3: + '{"message":"Valid data must be provided in the \'attributes\', \'events\', or \'purchases\' fields.","errors":[{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":0},{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":1},{"type":"\'quantity\' is not valid","input_array":"purchases","index":0}]}', +}; + +const expectedStatTags = { + errorCategory: 'network', + errorType: 'aborted', + destType: 'BRAZE', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'braze_v1_scenario_1', + name: 'braze', + description: + '[Proxy v1 API] :: Test for a valid request - 2 events and 1 purchase event are sent where the destination responds with 200 without any error', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [BrazeEvent1, BrazeEvent2], + purchases: [BrazePurchaseEvent], + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/valid_scenario1`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(3), + }, + ], + status: 200, + message: 'Request for braze Processed Successfully', + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_2', + name: 'braze', + description: + '[Proxy v1 API] :: Test for a invalid request - 2 events and 1 purchase event are sent where the destination responds with 200 with error for a one of the event and the purchase event', + successCriteria: 'Should return 200 with error for one of the event and the purchase event', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [{ ...BrazeEvent1, user_alias: undefined }, BrazeEvent2], // modifying first event to be invalid + purchases: [{ ...BrazePurchaseEvent, quantity: 'invalid quantity' }], // modifying purchase event to be invalid + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario1`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(3), + }, + ], + status: 200, + message: 'Request for braze Processed Successfully', + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_3', + name: 'braze', + description: '[Proxy v1 API] :: Test for an invalid request - all the payloads are invalid', + successCriteria: 'Should return 400 with error for all the payloads', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [ + { ...BrazeEvent1, user_alias: undefined }, + { ...BrazeEvent2, external_id: undefined }, + ], // modifying first event to be invalid + purchases: [{ ...BrazePurchaseEvent, quantity: 'invalid quantity' }], // modifying purchase event to be invalid + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario2`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(3), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 400', + status: 400, + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_4', + name: 'braze', + description: '[Proxy v1 API] :: Test for invalid auth scneario', + successCriteria: 'Should return 400 for all the payloads', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [BrazeEvent1, BrazeEvent2], + purchases: [BrazePurchaseEvent], + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario3`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(1), + }, + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(2), + }, + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(3), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 401', + status: 401, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/braze/dataDelivery/data.ts b/test/integrations/destinations/braze/dataDelivery/data.ts index 8162e75720..2596a4b959 100644 --- a/test/integrations/destinations/braze/dataDelivery/data.ts +++ b/test/integrations/destinations/braze/dataDelivery/data.ts @@ -1,6 +1,8 @@ import MockAdapter from 'axios-mock-adapter'; +import { testScenariosForV1API } from './business'; +import { otherScenariosV1 } from './other'; -export const data = [ +export const existingTestData = [ { name: 'braze', description: 'Test 0', @@ -629,7 +631,7 @@ export const data = [ }, output: { response: { - status: 401, + status: 200, body: { output: { status: 401, @@ -662,7 +664,6 @@ export const data = [ module: 'destination', workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', }, - authErrorCategory: '', message: 'Request failed for braze with status: 401', }, }, @@ -770,7 +771,7 @@ export const data = [ }, output: { response: { - status: 401, + status: 200, body: { output: { status: 401, @@ -840,7 +841,6 @@ export const data = [ module: 'destination', workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', }, - authErrorCategory: '', message: 'Request failed for braze with status: 401', }, }, @@ -848,3 +848,5 @@ export const data = [ }, }, ]; + +export const data = [...existingTestData, ...testScenariosForV1API, ...otherScenariosV1]; diff --git a/test/integrations/destinations/braze/dataDelivery/other.ts b/test/integrations/destinations/braze/dataDelivery/other.ts new file mode 100644 index 0000000000..9353899a65 --- /dev/null +++ b/test/integrations/destinations/braze/dataDelivery/other.ts @@ -0,0 +1,204 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +const expectedStatTags = { + errorCategory: 'network', + errorType: 'retryable', + destType: 'BRAZE', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', +}; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'braze_v1_other_scenario_1', + name: 'braze', + description: + '[Proxy v1 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_service_not_available', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: + '{"error":{"message":"Service Unavailable","description":"The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later."}}', + statusCode: 503, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 503', + status: 503, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_2', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_internal_server_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Internal Server Error"', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_3', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 504 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_gateway_time_out', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Gateway Timeout"', + statusCode: 504, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 504', + status: 504, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_4', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing null response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_response', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_5', + name: 'braze', + description: + '[Proxy v1 API] :: Scenario for testing null and no status response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_and_no_status', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/braze/network.ts b/test/integrations/destinations/braze/network.ts index 40d75c9d34..ae093ce1f4 100644 --- a/test/integrations/destinations/braze/network.ts +++ b/test/integrations/destinations/braze/network.ts @@ -524,4 +524,104 @@ const deleteNwData = [ }, }, ]; -export const networkCallsData = [...deleteNwData, ...dataDeliveryMocksData]; + +const BRAZE_USERS_TRACK_ENDPOINT = 'https://rest.iad-03.braze.com/users/track'; + +// New Mocks for Braze +const updatedDataDeliveryMocksData = [ + { + description: + 'Mock response from destination depicting a valid request for 2 valid events and 1 purchase event', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/valid_scenario1`, + method: 'POST', + }, + httpRes: { + data: { + events_processed: 2, + purchases_processed: 1, + message: 'success', + }, + status: 200, + }, + }, + + { + description: + 'Mock response from destination depicting a request with 1 valid and 1 invalid event and 1 invalid purchase event', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario1`, + method: 'POST', + }, + httpRes: { + data: { + events_processed: 1, + message: 'success', + errors: [ + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 1, + }, + { + type: "'quantity' is not valid", + input_array: 'purchases', + index: 0, + }, + ], + }, + status: 200, + }, + }, + + { + description: + 'Mock response from destination depicting a request with all the payloads are invalid', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario2`, + method: 'POST', + }, + httpRes: { + data: { + message: + "Valid data must be provided in the 'attributes', 'events', or 'purchases' fields.", + errors: [ + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 0, + }, + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 1, + }, + { + type: "'quantity' is not valid", + input_array: 'purchases', + index: 0, + }, + ], + }, + status: 400, + }, + }, + { + description: 'Mock response from destination depicting a request with invalid credentials', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario3`, + method: 'POST', + }, + httpRes: { + data: { + message: 'Invalid API Key', + }, + status: 401, + }, + }, +]; +export const networkCallsData = [ + ...deleteNwData, + ...dataDeliveryMocksData, + ...updatedDataDeliveryMocksData, +]; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts new file mode 100644 index 0000000000..e663f3212a --- /dev/null +++ b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts @@ -0,0 +1,606 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils'; + +// Boilerplate data for the test cases +// ====================================== + +const commonHeaders = { + Authorization: 'Bearer dummyApiKey', + 'Content-Type': 'application/json', +}; + +const encryptionInfo = { + kind: 'dfareporting#encryptionInfo', + encryptionSource: 'AD_SERVING', + encryptionEntityId: '3564523', + encryptionEntityType: 'DCM_ACCOUNT', +}; + +const testConversion1 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const testConversion2 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: { + kind: 'dfareporting#conversionsBatchInsertRequest', + encryptionInfo, + conversions: [testConversion1, testConversion2], + }, +}; + +const proxyMetdata1: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, +}; + +const proxyMetdata2: ProxyMetdata = { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, +}; + +const metadataArray = [proxyMetdata1, proxyMetdata2]; + +// Test scenarios for the test cases +// =================================== + +export const testScenariosForV0API = [ + { + id: 'cm360_v0_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Test for a valid request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully', + destinationResponse: { + response: { + hasFailures: false, + status: [ + { + conversion: testConversion1, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: testConversion2, + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_scenario_2', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for conversion 2', + successCriteria: 'Should return 400 with error and with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + status: 400, + message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation', + destinationResponse: { + response: { + hasFailures: true, + status: [ + { + conversion: testConversion1, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: testConversion2, + errors: [ + { + code: 'NOT_FOUND', + message: 'Floodlight config id: 213123123 was not found.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_scenario_3', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for both conversions', + successCriteria: 'Should return 400 with error and with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: + 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + status: 400, + message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation', + destinationResponse: { + response: { + hasFailures: true, + status: [ + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'INVALID_ARGUMENT', + message: 'Gclid is not valid.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'NOT_FOUND', + message: 'Floodlight config id: 213123123 was not found.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, +]; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'cm360_v1_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Test for a valid request - where the destination responds with 200 without any error', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', + destinationResponse: { + response: { + hasFailures: false, + status: [ + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, + }, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, + }, + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + response: [ + { + statusCode: 200, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'success', + }, + { + statusCode: 200, + metadata: { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'success', + }, + ], + }, + }, + }, + }, + }, + { + id: 'cm360_v1_scenario_2', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Test for a valid request - where the destination responds with 200 with error for conversion 2', + successCriteria: 'Should return 200 with partial failures within the response payload', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: + 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', + destinationResponse: { + response: { + hasFailures: true, + status: [ + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, + }, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'NOT_FOUND', + message: 'Floodlight config id: 213123123 was not found.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + response: [ + { + statusCode: 200, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'success', + }, + { + statusCode: 400, + metadata: { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'Floodlight config id: 213123123 was not found., ', + }, + ], + }, + }, + }, + }, + }, + { + id: 'cm360_v1_scenario_3', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for both conversions', + successCriteria: 'Should return 200 with all failures within the response payload', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: + 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', + destinationResponse: { + response: { + hasFailures: true, + status: [ + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'INVALID_ARGUMENT', + message: 'Gclid is not valid.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'NOT_FOUND', + message: 'Floodlight config id: 213123123 was not found.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + response: [ + { + statusCode: 400, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'Gclid is not valid., ', + }, + { + statusCode: 400, + metadata: { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'Floodlight config id: 213123123 was not found., ', + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts index 601ad56401..0373ca9992 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts @@ -1,604 +1,12 @@ +import { testScenariosForV0API, testScenariosForV1API } from './business'; +import { v0oauthScenarios, v1oauthScenarios } from './oauth'; +import { otherScenariosV0, otherScenariosV1 } from './other'; + export const data = [ - { - name: 'campaign_manager', - description: 'Sucess insert request V0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437689/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: '[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully', - destinationResponse: { - response: { - hasFailures: false, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'campaign_manager', - description: 'Failure insert request', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437690/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - status: 400, - message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation', - statTags: { - errorCategory: 'network', - errorType: 'aborted', - destType: 'CAMPAIGN_MANAGER', - module: 'destination', - implementation: 'native', - feature: 'dataDelivery', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - }, - destinationResponse: { - response: { - hasFailures: true, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - errors: [ - { - code: 'NOT_FOUND', - message: 'Floodlight config id: 213123123 was not found.', - kind: 'dfareporting#conversionError', - }, - ], - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'campaign_manager', - description: 'Failure insert request Aborted', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437691/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - status: 400, - message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation', - statTags: { - errorCategory: 'network', - errorType: 'aborted', - destType: 'CAMPAIGN_MANAGER', - module: 'destination', - implementation: 'native', - feature: 'dataDelivery', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - }, - destinationResponse: { - response: { - hasFailures: true, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - errors: [ - { - code: 'INVALID_ARGUMENT', - message: 'Floodlight config id: 213123123 was not found.', - kind: 'dfareporting#conversionError', - }, - ], - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'campaign_manager', - description: 'Sucess and fail insert request v1', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437692/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 8, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - metadata: [ - { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - { - jobId: 3, - attemptNum: 1, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - ], - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', - destinationResponse: { - response: { - hasFailures: true, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - errors: [ - { - code: 'INVALID_ARGUMENT', - kind: 'dfareporting#conversionError', - message: 'Floodlight config id: 213123123 was not found.', - }, - ], - }, - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 8, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - rudderJobMetadata: [ - { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - { - jobId: 3, - attemptNum: 1, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - ], - }, - response: [ - { - error: 'Floodlight config id: 213123123 was not found., ', - statusCode: 400, - metadata: { - attemptNum: 0, - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - jobId: 2, - secret: { - access_token: 'secret', - developer_token: 'developer_Token', - refresh_token: 'refresh', - }, - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - userId: '', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - }, - }, - { - error: 'success', - metadata: { - attemptNum: 1, - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - jobId: 3, - secret: { - access_token: 'secret', - developer_token: 'developer_Token', - refresh_token: 'refresh', - }, - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - userId: '', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - }, - statusCode: 200, - }, - ], - }, - }, - }, - }, - }, - { - name: 'campaign_manager', - description: 'Sucess insert request v1', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/43770/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - metadata: { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', - destinationResponse: { - response: { - hasFailures: false, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - rudderJobMetadata: { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - }, - response: [ - { - error: 'success', - statusCode: 200, - }, - ], - }, - }, - }, - }, - }, + ...testScenariosForV0API, + ...testScenariosForV1API, + ...v0oauthScenarios, + ...v1oauthScenarios, + ...otherScenariosV0, + ...otherScenariosV1, ]; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts new file mode 100644 index 0000000000..929af485d8 --- /dev/null +++ b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts @@ -0,0 +1,558 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload, generateProxyV0Payload } from '../../../testUtils'; +// Boilerplat data for the test cases +// ====================================== + +const commonHeaders = { + Authorization: 'Bearer dummyApiKey', + 'Content-Type': 'application/json', +}; + +const encryptionInfo = { + kind: 'dfareporting#encryptionInfo', + encryptionSource: 'AD_SERVING', + encryptionEntityId: '3564523', + encryptionEntityType: 'DCM_ACCOUNT', +}; + +const testConversion1 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const testConversion2 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: { + kind: 'dfareporting#conversionsBatchInsertRequest', + encryptionInfo, + conversions: [testConversion1, testConversion2], + }, +}; + +// Test scenarios for the test cases +// =================================== + +export const v0oauthScenarios = [ + { + id: 'cm360_v0_oauth_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Oauth where valid credentials are missing as mock response from destination', + successCriteria: + 'Since the error from the destination is 401 - the proxy should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_credentials_missing', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + error: { + code: 401, + message: + 'Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.', + errors: [ + { + message: 'Login Required.', + domain: 'global', + reason: 'required', + location: 'Authorization', + locationType: 'header', + }, + ], + status: 'UNAUTHENTICATED', + details: [ + { + '@type': 'type.googleapis.com/google.rpc.ErrorInfo', + reason: 'CREDENTIALS_MISSING', + domain: 'googleapis.com', + metadata: { + method: 'google.ads.xfa.op.v4.DfareportingConversions.Batchinsert', + service: 'googleapis.com', + }, + }, + ], + }, + }, + status: 401, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'REFRESH_TOKEN', + }, + }, + }, + }, + }, + { + id: 'cm360_v0_oauth_scenario_2', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Oauth where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_access_token_scope_insufficient', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: Request had insufficient authentication scopes. during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + error: { + code: 403, + message: 'Request had insufficient authentication scopes.', + errors: [ + { + message: 'Insufficient Permission', + domain: 'global', + reason: 'insufficientPermissions', + }, + ], + status: 'PERMISSION_DENIED', + details: [ + { + '@type': 'type.googleapis.com/google.rpc.ErrorInfo', + reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT', + domain: 'googleapis.com', + metadata: { + service: 'gmail.googleapis.com', + method: 'caribou.api.proto.MailboxService.GetProfile', + }, + }, + ], + }, + }, + status: 403, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + }, + }, + }, + }, + }, + { + id: 'cm360_v0_oauth_scenario_3', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Oauth where google.auth.exceptions.RefreshError invalid_grant error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_invalid_grant', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: invalid_grant during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + error: { + code: 403, + message: 'invalid_grant', + error_description: 'Bad accesss', + }, + }, + status: 403, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + }, + }, + }, + }, + }, + { + id: 'cm360_v0_oauth_scenario_4', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Oauth where google.auth.exceptions.RefreshError refresh error as mock response from destination', + successCriteria: 'Should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_refresh_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + error: 'unauthorized', + error_description: 'Access token expired: 2020-10-20T12:00:00.000Z', + }, + status: 401, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'REFRESH_TOKEN', + }, + }, + }, + }, + }, +]; + +export const v1oauthScenarios: ProxyV1TestData[] = [ + { + id: 'cm360_v1_oauth_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Oauth where valid credentials are missing as mock response from destination', + successCriteria: + 'Since the error from the destination is 401 - the proxy should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_credentials_missing', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + response: [ + { + error: + '{"error":{"code":401,"message":"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.","errors":[{"message":"Login Required.","domain":"global","reason":"required","location":"Authorization","locationType":"header"}],"status":"UNAUTHENTICATED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"CREDENTIALS_MISSING","domain":"googleapis.com","metadata":{"method":"google.ads.xfa.op.v4.DfareportingConversions.Batchinsert","service":"googleapis.com"}}]}}', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'REFRESH_TOKEN', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_oauth_scenario_2', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Oauth where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_access_token_scope_insufficient', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + response: [ + { + error: + '{"error":{"code":403,"message":"Request had insufficient authentication scopes.","errors":[{"message":"Insufficient Permission","domain":"global","reason":"insufficientPermissions"}],"status":"PERMISSION_DENIED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"ACCESS_TOKEN_SCOPE_INSUFFICIENT","domain":"googleapis.com","metadata":{"service":"gmail.googleapis.com","method":"caribou.api.proto.MailboxService.GetProfile"}}]}}', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_oauth_scenario_3', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Oauth where google.auth.exceptions.RefreshError invalid_grant error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_invalid_grant', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + response: [ + { + error: + '{"error":{"code":403,"message":"invalid_grant","error_description":"Bad accesss"}}', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_oauth_scenario_4', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Oauth where google.auth.exceptions.RefreshError refresh error as mock response from destination', + successCriteria: 'Should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_refresh_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + response: [ + { + error: + '{"error":"unauthorized","error_description":"Access token expired: 2020-10-20T12:00:00.000Z"}', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'REFRESH_TOKEN', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts new file mode 100644 index 0000000000..709f55a4c0 --- /dev/null +++ b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts @@ -0,0 +1,529 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils'; + +export const otherScenariosV0 = [ + { + id: 'cm360_v0_other_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_service_not_available', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: Service Unavailable during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + error: { + message: 'Service Unavailable', + description: + 'The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later.', + }, + }, + status: 503, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_other_scenario_2', + name: 'campaign_manager', + description: '[Proxy v0 API] :: Scenario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_internal_server_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: 'Internal Server Error', + status: 500, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_other_scenario_3', + name: 'campaign_manager', + description: '[Proxy v0 API] :: Scenario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_gateway_time_out', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: 'Gateway Timeout', + status: 504, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_other_scenario_4', + name: 'campaign_manager', + description: '[Proxy v0 API] :: Scenario for testing null response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_null_response', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: '', + status: 500, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_other_scenario_5', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Scenario for testing null and no status response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_null_and_no_status', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: '', + status: 500, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, +]; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'cm360_v1_other_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_service_not_available', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: + '{"error":{"message":"Service Unavailable","description":"The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later."}}', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_other_scenario_2', + name: 'campaign_manager', + description: '[Proxy v1 API] :: Scenario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_internal_server_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Internal Server Error"', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_other_scenario_3', + name: 'campaign_manager', + description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_gateway_time_out', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Gateway Timeout"', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_other_scenario_4', + name: 'campaign_manager', + description: '[Proxy v1 API] :: Scenario for testing null response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_response', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_other_scenario_5', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Scenario for testing null and no status response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_and_no_status', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/campaign_manager/network.ts b/test/integrations/destinations/campaign_manager/network.ts index ddecbaf8fa..b7c2301248 100644 --- a/test/integrations/destinations/campaign_manager/network.ts +++ b/test/integrations/destinations/campaign_manager/network.ts @@ -1,49 +1,70 @@ -const Data = [ +const commonHeaders = { + Authorization: 'Bearer dummyApiKey', + 'Content-Type': 'application/json', +}; + +const encryptionInfo = { + kind: 'dfareporting#encryptionInfo', + encryptionSource: 'AD_SERVING', + encryptionEntityId: '3564523', + encryptionEntityType: 'DCM_ACCOUNT', +}; + +const testConversion1 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const testConversion2 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: { + kind: 'dfareporting#conversionsBatchInsertRequest', + encryptionInfo, + conversions: [testConversion1, testConversion2], + }, +}; + +// MOCK DATA +const businessMockData = [ { + description: 'Mock response from destination depicting a valid request', httpReq: { method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437689/conversions/batchinsert', + url: 'https://dfareporting.googleapis.com/test_url_for_valid_request', data: { kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', + encryptionInfo, + conversions: [testConversion1, testConversion2], }, + headers: commonHeaders, }, httpRes: { data: { hasFailures: false, status: [ { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, + conversion: testConversion1, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: testConversion2, kind: 'dfareporting#conversionStatus', }, ], @@ -54,50 +75,28 @@ const Data = [ }, }, { + description: + 'Mock response from destination depicting a request with 1 valid and 1 invalid conversion', httpReq: { method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437690/conversions/batchinsert', + url: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2', data: { kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', + encryptionInfo, + conversions: [testConversion1, testConversion2], }, + headers: commonHeaders, }, httpRes: { data: { hasFailures: true, status: [ { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, + conversion: testConversion1, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: testConversion2, errors: [ { code: 'NOT_FOUND', @@ -115,185 +114,37 @@ const Data = [ }, }, { + description: 'Mock response from destination depicting a request with 2 invalid conversions', httpReq: { method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/43770/conversions/batchinsert', - data: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - }, - httpRes: { - data: { - hasFailures: false, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - statusText: 'OK', - }, - }, - { - httpReq: { - method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437692/conversions/batchinsert', + url: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions', data: { kind: 'dfareporting#conversionsBatchInsertRequest', - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 8, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', + encryptionInfo, + conversions: [testConversion1, testConversion2], }, + headers: commonHeaders, }, httpRes: { data: { hasFailures: true, status: [ { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, + conversion: testConversion1, errors: [ { code: 'INVALID_ARGUMENT', - message: 'Floodlight config id: 213123123 was not found.', + message: 'Gclid is not valid.', kind: 'dfareporting#conversionError', }, ], kind: 'dfareporting#conversionStatus', }, { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 8, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - statusText: 'OK', - }, - }, - { - httpReq: { - method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437691/conversions/batchinsert', - data: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - }, - httpRes: { - data: { - hasFailures: true, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, + conversion: testConversion2, errors: [ { - code: 'INVALID_ARGUMENT', + code: 'NOT_FOUND', message: 'Floodlight config id: 213123123 was not found.', kind: 'dfareporting#conversionError', }, @@ -308,4 +159,5 @@ const Data = [ }, }, ]; -export const networkCallsData = [...Data]; + +export const networkCallsData = [...businessMockData]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/business.ts b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts new file mode 100644 index 0000000000..f30bf73d7a --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts @@ -0,0 +1,255 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; +export const headers = { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', +}; +export const params = { + destination: 'criteo_audience', +}; +const method = 'PATCH'; + +export const V1BusinessTestScenarion: ProxyV1TestData[] = [ + { + id: 'criteo_audience_business_0', + name: 'criteo_audience', + description: '[Business]:: Test for gum type audience with gumCallerId with success response', + successCriteria: 'Should return a 200 status code with a success message', + scenario: 'business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'remove', + identifierType: 'gum', + identifiers: ['sample_gum3'], + internalIdentifiers: false, + gumCallerId: '329739', + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: generateMetadata(1), + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_1', + name: 'criteo_audience', + scenario: 'business', + description: '[Business]:: Test for email type audience to add users with success response', + successCriteria: 'Should return a 200 status code with a success message', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + method: 'POST', + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'email', + internalIdentifiers: false, + identifiers: [ + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + ], + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', + }, + [generateMetadata(2)], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: generateMetadata(2), + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_2', + name: 'criteo_audience', + scenario: 'business', + description: '[Business]:: Test for mobile type audience to remove users with success response', + successCriteria: 'Should return a 200 status code with a success message', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + method: 'POST', + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'remove', + identifierType: 'madid', + internalIdentifiers: false, + identifiers: [ + 'sample_madid', + 'sample_madid_1', + 'sample_madid_2', + 'sample_madid_10', + 'sample_madid_13', + 'sample_madid_11', + 'sample_madid_12', + ], + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34893/contactlist', + }, + [generateMetadata(3)], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: generateMetadata(3), + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_3', + name: 'criteo_audience', + scenario: 'business', + description: '[Business]:: Test for mobile type audience where audienceId is invalid', + successCriteria: + 'Should return a 400 status code with an error audience-invalid. It should also have the invalid audienceId in the error message as follows: "Audience is invalid"', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + method: 'POST', + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', + }, + [generateMetadata(4)], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'AudienceId is Invalid. Please Provide Valid AudienceId', + response: [ + { + error: + '{"errors":[{"traceIdentifier":"80a1a0ba3981b04da847d05700752c77","type":"authorization","code":"audience-invalid"}]}', + metadata: generateMetadata(4), + statusCode: 400, + }, + ], + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + meta: 'instrumentation', + module: 'destination', + }, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts index fb5b689a96..c603ef6664 100644 --- a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts +++ b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts @@ -1,4 +1,9 @@ -export const data = [ +import { generateMetadata } from '../../../testUtils'; +import { V1BusinessTestScenarion } from './business'; +import { v1OauthScenarios } from './oauth'; +import { v1OtherScenarios } from './other'; + +const v0testCases = [ { name: 'criteo_audience', description: 'Test 0', @@ -38,6 +43,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(1), + destinationConfig: {}, }, method: 'POST', }, @@ -70,7 +78,7 @@ export const data = [ version: '1', type: 'REST', method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', + endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken', headers: { Authorization: 'Bearer success_access_token', 'Content-Type': 'application/json', @@ -96,6 +104,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(2), + destinationConfig: {}, }, method: 'POST', }, @@ -123,8 +134,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'aborted', feature: 'dataDelivery', implementation: 'native', @@ -147,7 +158,7 @@ export const data = [ version: '1', type: 'REST', method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', + endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken', headers: { Authorization: 'Bearer success_access_token', 'Content-Type': 'application/json', @@ -173,6 +184,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(3), + destinationConfig: {}, }, method: 'POST', }, @@ -200,8 +214,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'aborted', feature: 'dataDelivery', implementation: 'native', @@ -250,6 +264,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(4), + destinationConfig: {}, }, method: 'POST', }, @@ -275,8 +292,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'aborted', feature: 'dataDelivery', implementation: 'native', @@ -327,6 +344,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(5), + destinationConfig: {}, }, method: 'POST', }, @@ -352,8 +372,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', feature: 'dataDelivery', implementation: 'native', errorType: 'retryable', @@ -403,6 +423,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(6), + destinationConfig: {}, }, method: 'POST', }, @@ -421,8 +444,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'throttled', feature: 'dataDelivery', implementation: 'native', @@ -472,6 +495,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(7), + destinationConfig: {}, }, method: 'POST', }, @@ -492,8 +518,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'aborted', feature: 'dataDelivery', implementation: 'native', @@ -506,3 +532,10 @@ export const data = [ }, }, ]; + +export const data = [ + ...v0testCases, + ...V1BusinessTestScenarion, + ...v1OauthScenarios, + ...v1OtherScenarios, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts new file mode 100644 index 0000000000..982397f7c3 --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts @@ -0,0 +1,133 @@ +import { params, headers } from './business'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; + +const commonStatTags = { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; + +export const v1OauthScenarios = [ + { + id: 'criteo_audience_oauth_0', + name: 'criteo_audience', + description: '[OAUTH]:: Test expired access token', + successCriteria: 'Should return a 401 status code with authErrorCategory as REFRESH_TOKEN', + scenario: 'oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method: 'PATCH', + endpoint: + 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + authErrorCategory: 'REFRESH_TOKEN', + response: [ + { + error: + 'The authorization token has expired during criteo_audience response transformation', + metadata: generateMetadata(1), + statusCode: 401, + }, + ], + message: + 'The authorization token has expired during criteo_audience response transformation', + statTags: commonStatTags, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_oauth_1', + name: 'criteo_audience', + description: '[OAUTH]:: Test invalid access token', + successCriteria: + 'We should get a 401 status code with errorCode authorization-token-invalid. As we need to refresh the token for these conditions, authErrorCategory should be REFRESH_TOKEN', + scenario: 'oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method: 'PATCH', + endpoint: + 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken', + }, + [generateMetadata(2)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + authErrorCategory: 'REFRESH_TOKEN', + response: [ + { + error: + 'The authorization header is invalid during criteo_audience response transformation', + metadata: generateMetadata(2), + statusCode: 401, + }, + ], + statTags: commonStatTags, + message: + 'The authorization header is invalid during criteo_audience response transformation', + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/other.ts b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts new file mode 100644 index 0000000000..f3a0688f88 --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts @@ -0,0 +1,196 @@ +import { params, headers } from './business'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; + +export const v1OtherScenarios = [ + { + id: 'criteo_audience_other_0', + name: 'criteo_audience', + description: '[Other]:: Test for checking service unavailable scenario', + successCriteria: 'Should return a 500 status code with', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + endpoint: 'https://random_test_url/test_for_internal_server_error', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + response: [ + { + error: '""', + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + message: 'Request Failed: during criteo_audience response transformation (Retryable)', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + feature: 'dataDelivery', + implementation: 'native', + errorType: 'retryable', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_other_1', + name: 'criteo_audience', + description: '[Other]:: Test for checking throttling scenario', + successCriteria: 'Should return a 429 status code', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + endpoint: 'https://random_test_url/test_for_too_many_requests', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + }, + [generateMetadata(2)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 429, + response: [ + { + error: '{}', + metadata: generateMetadata(2), + statusCode: 429, + }, + ], + message: + 'Request Failed: during criteo_audience response transformation - due to Request Limit exceeded, (Throttled)', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + feature: 'dataDelivery', + implementation: 'native', + errorType: 'throttled', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_other_2', + name: 'criteo_audience', + description: '[Other]:: Test for checking unknown error scenario', + successCriteria: 'Should return a 410 status code and abort the event', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + endpoint: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', + }, + [generateMetadata(3)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + response: [ + { + error: '{"message":"unknown error"}', + metadata: generateMetadata(3), + statusCode: 400, + }, + ], + message: + 'Request Failed: during criteo_audience response transformation with status "410" due to "{"message":"unknown error"}", (Aborted) ', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/network.ts b/test/integrations/destinations/criteo_audience/network.ts index 959e8a2112..7ccf649e2a 100644 --- a/test/integrations/destinations/criteo_audience/network.ts +++ b/test/integrations/destinations/criteo_audience/network.ts @@ -1,3 +1,23 @@ +const headers = { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', +}; +const params = { destination: 'criteo_audience' }; +const method = 'PATCH'; +const commonData = { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, +}; + export const networkCallsData = [ { httpReq: { @@ -14,117 +34,74 @@ export const networkCallsData = [ }, }, }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + params, + headers, + method, }, httpRes: { status: 200 }, }, { httpReq: { - url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', + url: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', data: { data: { type: 'ContactlistAmendment', attributes: { operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + identifierType: 'email', internalIdentifiers: false, + identifiers: [ + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + ], }, }, }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', - }, - httpRes: { - code: '400', - data: { - errors: [ - { - traceIdentifier: '80a1a0ba3981b04da847d05700752c77', - type: 'authorization', - code: 'authorization-token-expired', - instance: '/2022-10/audiences/123/contactlist', - title: 'The authorization token has expired', - }, - ], - }, - status: 401, + params, + headers, + method, }, + httpRes: { status: 200 }, }, { httpReq: { - url: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', + url: 'https://api.criteo.com/2022-10/audiences/34893/contactlist', data: { data: { type: 'ContactlistAmendment', attributes: { - operation: 'add', + operation: 'remove', identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], internalIdentifiers: false, + identifiers: [ + 'sample_madid', + 'sample_madid_1', + 'sample_madid_2', + 'sample_madid_10', + 'sample_madid_13', + 'sample_madid_11', + 'sample_madid_12', + ], }, }, }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', - }, - httpRes: { - code: '400', - data: { - errors: [ - { - traceIdentifier: '80a1a0ba3981b04da847d05700752c77', - type: 'authorization', - code: 'authorization-token-invalid', - instance: '/2022-10/audiences/123/contactlist', - title: 'The authorization header is invalid', - }, - ], - }, - status: 401, + params, + headers, + method, }, + httpRes: { status: 200 }, }, { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '400', @@ -143,25 +120,10 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34897/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '500', @@ -180,50 +142,20 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34898/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '429', data: {}, status: 429 }, }, { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '400', data: { message: 'unknown error' }, status: 410 }, }, diff --git a/test/integrations/destinations/klaviyo/processor/ecomTestData.ts b/test/integrations/destinations/klaviyo/processor/ecomTestData.ts index fab4cf85ce..34eff45232 100644 --- a/test/integrations/destinations/klaviyo/processor/ecomTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/ecomTestData.ts @@ -1,10 +1,23 @@ -import { overrideDestination, transformResultBuilder } from '../../../testUtils'; +import { overrideDestination, transformResultBuilder, generateMetadata } from '../../../testUtils'; +import { ProcessorTestData } from '../../../testTypes'; +import { Destination } from '../../../../../src/types'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; const commonTraits = { @@ -26,7 +39,7 @@ const commonOutputHeaders = { revision: '2023-02-22', }; -export const ecomTestData = [ +export const ecomTestData: ProcessorTestData[] = [ { id: 'klaviyo-ecom-test-1', name: 'klaviyo', @@ -64,6 +77,7 @@ export const ecomTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }, + metadata: generateMetadata(1), }, ], }, @@ -108,6 +122,7 @@ export const ecomTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, @@ -170,6 +185,7 @@ export const ecomTestData = [ }, anonymousId: '9c6bd77ea9da3e68', }, + metadata: generateMetadata(2), }, ], }, @@ -220,6 +236,7 @@ export const ecomTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(2), }, ], }, @@ -280,6 +297,7 @@ export const ecomTestData = [ }, originalTimestamp: '2021-01-25T15:32:56.409Z', }, + metadata: generateMetadata(3), }, ], }, @@ -336,6 +354,7 @@ export const ecomTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(3), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/groupTestData.ts b/test/integrations/destinations/klaviyo/processor/groupTestData.ts index 031c949c4b..0002f7ce90 100644 --- a/test/integrations/destinations/klaviyo/processor/groupTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/groupTestData.ts @@ -1,10 +1,27 @@ -import { generateSimplifiedGroupPayload, transformResultBuilder } from '../../../testUtils'; +import { Destination } from '../../../../../src/types'; +import { ProcessorTestData } from '../../../testTypes'; +import { + generateMetadata, + generateSimplifiedGroupPayload, + transformResultBuilder, +} from '../../../testUtils'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; const headers = { @@ -16,7 +33,7 @@ const headers = { const commonEndpoint = 'https://a.klaviyo.com/api/profile-subscription-bulk-create-jobs'; -export const groupTestData = [ +export const groupTestData: ProcessorTestData[] = [ { id: 'klaviyo-group-test-1', name: 'klaviyo', @@ -47,6 +64,7 @@ export const groupTestData = [ }, timestamp: '2020-01-21T00:21:34.208Z', }), + metadata: generateMetadata(1), }, ], }, @@ -74,6 +92,7 @@ export const groupTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, @@ -109,6 +128,7 @@ export const groupTestData = [ }, timestamp: '2020-01-21T00:21:34.208Z', }), + metadata: generateMetadata(2), }, ], }, @@ -126,8 +146,11 @@ export const groupTestData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 400, + metadata: generateMetadata(2), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/identifyTestData.ts b/test/integrations/destinations/klaviyo/processor/identifyTestData.ts index 8b5503fad9..f632cb767c 100644 --- a/test/integrations/destinations/klaviyo/processor/identifyTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/identifyTestData.ts @@ -3,13 +3,27 @@ import { overrideDestination, transformResultBuilder, generateSimplifiedIdentifyPayload, + generateMetadata, } from '../../../testUtils'; +import { ProcessorTestData } from '../../../testTypes'; +import { Destination } from '../../../../../src/types'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; const commonTraits = { @@ -81,7 +95,7 @@ const originalTimestamp = '2021-01-03T17:02:53.193Z'; const commonUserUpdateEndpoint = 'https://a.klaviyo.com/api/profiles/01GW3PHVY0MTCDGS0A1612HARX'; const subscribeEndpoint = 'https://a.klaviyo.com/api/profile-subscription-bulk-create-jobs'; -export const identifyData = [ +export const identifyData: ProcessorTestData[] = [ { id: 'klaviyo-identify-test-1', name: 'klaviyo', @@ -108,6 +122,7 @@ export const identifyData = [ userId, sentAt, }), + metadata: generateMetadata(1), }, ], }, @@ -131,6 +146,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(1), }, { output: transformResultBuilder({ @@ -146,6 +162,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, @@ -184,6 +201,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(2), }, ], }, @@ -215,6 +233,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(2), }, { output: transformResultBuilder({ @@ -230,6 +249,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(2), }, ], }, @@ -249,12 +269,10 @@ export const identifyData = [ request: { body: [ { - destination: { - Config: { - publicApiKey: 'dummyPublicApiKey', - privateApiKey: 'dummyPrivateApiKeyforfailure', - }, - }, + destination: overrideDestination(destination, { + publicApiKey: 'dummyPublicApiKey', + privateApiKey: 'dummyPrivateApiKeyforfailure', + }), message: generateSimplifiedIdentifyPayload({ sentAt, userId, @@ -267,6 +285,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(3), }, ], }, @@ -285,8 +304,11 @@ export const identifyData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 500, + metadata: generateMetadata(3), }, ], }, @@ -319,6 +341,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(4), }, ], }, @@ -342,6 +365,7 @@ export const identifyData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(4), }, ], }, @@ -371,6 +395,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(5), }, ], }, @@ -402,6 +427,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(5), }, { output: transformResultBuilder({ @@ -417,6 +443,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(5), }, ], }, @@ -450,6 +477,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(6), }, ], }, @@ -476,6 +504,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(6), }, { output: transformResultBuilder({ @@ -491,6 +520,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(6), }, ], }, @@ -524,6 +554,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(7), }, ], }, @@ -541,8 +572,11 @@ export const identifyData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 400, + metadata: generateMetadata(7), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/screenTestData.ts b/test/integrations/destinations/klaviyo/processor/screenTestData.ts index 3779747a4e..0a20110236 100644 --- a/test/integrations/destinations/klaviyo/processor/screenTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/screenTestData.ts @@ -1,13 +1,30 @@ -import { generateSimplifiedPageOrScreenPayload, transformResultBuilder } from '../../../testUtils'; +import { Destination } from '../../../../../src/types'; +import { ProcessorTestData } from '../../../testTypes'; +import { + generateMetadata, + generateSimplifiedPageOrScreenPayload, + transformResultBuilder, +} from '../../../testUtils'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; -export const screenTestData = [ +export const screenTestData: ProcessorTestData[] = [ { id: 'klaviyo-screen-test-1', name: 'klaviyo', @@ -47,6 +64,7 @@ export const screenTestData = [ }, 'screen', ), + metadata: generateMetadata(1), }, ], }, @@ -89,6 +107,7 @@ export const screenTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/trackTestData.ts b/test/integrations/destinations/klaviyo/processor/trackTestData.ts index f3bbfb96b9..3bc2b1747a 100644 --- a/test/integrations/destinations/klaviyo/processor/trackTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/trackTestData.ts @@ -1,15 +1,29 @@ +import { Destination } from '../../../../../src/types'; +import { ProcessorTestData } from '../../../testTypes'; import { + generateMetadata, generateSimplifiedTrackPayload, generateTrackPayload, overrideDestination, transformResultBuilder, } from '../../../testUtils'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; const commonTraits = { @@ -33,7 +47,7 @@ const commonOutputHeaders = { const eventEndPoint = 'https://a.klaviyo.com/api/events'; -export const trackTestData = [ +export const trackTestData: ProcessorTestData[] = [ { id: 'klaviyo-track-test-1', name: 'klaviyo', @@ -71,6 +85,7 @@ export const trackTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }), + metadata: generateMetadata(1), }, ], }, @@ -110,6 +125,7 @@ export const trackTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, @@ -151,6 +167,7 @@ export const trackTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }), + metadata: generateMetadata(2), }, ], }, @@ -187,6 +204,7 @@ export const trackTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(2), }, ], }, @@ -223,6 +241,7 @@ export const trackTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }), + metadata: generateMetadata(3), }, ], }, @@ -256,6 +275,7 @@ export const trackTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(3), }, ], }, @@ -289,6 +309,7 @@ export const trackTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }), + metadata: generateMetadata(4), }, ], }, @@ -306,8 +327,11 @@ export const trackTestData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 400, + metadata: generateMetadata(4), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/validationTestData.ts b/test/integrations/destinations/klaviyo/processor/validationTestData.ts index 59556cfe5f..801e03d541 100644 --- a/test/integrations/destinations/klaviyo/processor/validationTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/validationTestData.ts @@ -1,4 +1,26 @@ -export const validationTestData = [ +import { Destination } from '../../../../../src/types'; +import { ProcessorTestData } from '../../../testTypes'; +import { generateMetadata } from '../../../testUtils'; + +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, + Config: { + publicApiKey: 'dummyPublicApiKey', + privateApiKey: 'dummyPrivateApiKey', + }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], +}; + +export const validationTestData: ProcessorTestData[] = [ { id: 'klaviyo-validation-test-1', name: 'klaviyo', @@ -13,12 +35,7 @@ export const validationTestData = [ request: { body: [ { - destination: { - Config: { - publicApiKey: 'dummyPublicApiKey', - privateApiKey: 'dummyPrivateApiKey', - }, - }, + destination, message: { userId: 'user123', type: 'random', @@ -35,6 +52,7 @@ export const validationTestData = [ }, timestamp: '2020-01-21T00:21:34.208Z', }, + metadata: generateMetadata(1), }, ], }, @@ -52,8 +70,11 @@ export const validationTestData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 400, + metadata: generateMetadata(1), }, ], }, diff --git a/test/integrations/destinations/klaviyo/router/data.ts b/test/integrations/destinations/klaviyo/router/data.ts index 818089a722..8866a8a546 100644 --- a/test/integrations/destinations/klaviyo/router/data.ts +++ b/test/integrations/destinations/klaviyo/router/data.ts @@ -1,4 +1,184 @@ -export const data = [ +import { Destination, RouterTransformationRequest } from '../../../../../src/types'; +import { RouterTestData } from '../../../testTypes'; +import { generateMetadata } from '../../../testUtils'; + +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, + Config: { + publicApiKey: 'dummyPublicApiKey', + privateApiKey: 'dummyPrivateApiKey', + }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], +}; + +const routerRequest: RouterTransformationRequest = { + input: [ + { + destination, + metadata: generateMetadata(1), + message: { + type: 'identify', + sentAt: '2021-01-03T17:02:53.195Z', + userId: 'test', + channel: 'web', + context: { + os: { name: '', version: '' }, + app: { + name: 'RudderLabs JavaScript SDK', + build: '1.0.0', + version: '1.1.11', + namespace: 'com.rudderlabs.javascript', + }, + traits: { + firstName: 'Test', + lastName: 'Rudderlabs', + email: 'test@rudderstack.com', + phone: '+12 345 578 900', + userId: 'Testc', + title: 'Developer', + organization: 'Rudder', + city: 'Tokyo', + region: 'Kanto', + country: 'JP', + zip: '100-0001', + Flagged: false, + Residence: 'Shibuya', + properties: { consent: ['email', 'sms'] }, + }, + locale: 'en-US', + screen: { density: 2 }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.1.11' }, + campaign: {}, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', + }, + rudderId: '8f8fa6b5-8e24-489c-8e22-61f23f2e364f', + messageId: '2116ef8c-efc3-4ca4-851b-02ee60dad6ff', + anonymousId: '97c46c81-3140-456d-b2a9-690d70aaca35', + integrations: { All: true }, + originalTimestamp: '2021-01-03T17:02:53.193Z', + }, + }, + { + destination, + metadata: generateMetadata(2), + message: { + type: 'identify', + sentAt: '2021-01-03T17:02:53.195Z', + userId: 'test', + channel: 'web', + context: { + os: { name: '', version: '' }, + app: { + name: 'RudderLabs JavaScript SDK', + build: '1.0.0', + version: '1.1.11', + namespace: 'com.rudderlabs.javascript', + }, + traits: { + firstName: 'Test', + lastName: 'Rudderlabs', + email: 'test@rudderstack.com', + phone: '+12 345 578 900', + userId: 'test', + title: 'Developer', + organization: 'Rudder', + city: 'Tokyo', + region: 'Kanto', + country: 'JP', + zip: '100-0001', + Flagged: false, + Residence: 'Shibuya', + properties: { listId: 'XUepkK', subscribe: true, consent: ['email', 'sms'] }, + }, + locale: 'en-US', + screen: { density: 2 }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.1.11' }, + campaign: {}, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', + }, + rudderId: '8f8fa6b5-8e24-489c-8e22-61f23f2e364f', + messageId: '2116ef8c-efc3-4ca4-851b-02ee60dad6ff', + anonymousId: '97c46c81-3140-456d-b2a9-690d70aaca35', + integrations: { All: true }, + originalTimestamp: '2021-01-03T17:02:53.193Z', + }, + }, + { + destination, + metadata: generateMetadata(3), + message: { + userId: 'user123', + type: 'group', + groupId: 'XUepkK', + traits: { subscribe: true }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: ['email'], + }, + ip: '14.5.67.21', + library: { name: 'http' }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + { + destination, + metadata: generateMetadata(4), + message: { + userId: 'user123', + type: 'random', + groupId: 'XUepkK', + traits: { subscribe: true }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: 'email', + }, + ip: '14.5.67.21', + library: { name: 'http' }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + { + destination, + metadata: generateMetadata(5), + message: { + userId: 'user123', + type: 'group', + groupId: '', + traits: { subscribe: true }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: 'email', + }, + ip: '14.5.67.21', + library: { name: 'http' }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + ], + destType: 'klaviyo', +}; + +export const data: RouterTestData[] = [ { id: 'klaviyo-router-test-1', name: 'klaviyo', @@ -10,173 +190,7 @@ export const data = [ version: 'v0', input: { request: { - body: { - input: [ - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 1, userId: 'u1' }, - message: { - type: 'identify', - sentAt: '2021-01-03T17:02:53.195Z', - userId: 'test', - channel: 'web', - context: { - os: { name: '', version: '' }, - app: { - name: 'RudderLabs JavaScript SDK', - build: '1.0.0', - version: '1.1.11', - namespace: 'com.rudderlabs.javascript', - }, - traits: { - firstName: 'Test', - lastName: 'Rudderlabs', - email: 'test@rudderstack.com', - phone: '+12 345 578 900', - userId: 'Testc', - title: 'Developer', - organization: 'Rudder', - city: 'Tokyo', - region: 'Kanto', - country: 'JP', - zip: '100-0001', - Flagged: false, - Residence: 'Shibuya', - properties: { consent: ['email', 'sms'] }, - }, - locale: 'en-US', - screen: { density: 2 }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.1.11' }, - campaign: {}, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', - }, - rudderId: '8f8fa6b5-8e24-489c-8e22-61f23f2e364f', - messageId: '2116ef8c-efc3-4ca4-851b-02ee60dad6ff', - anonymousId: '97c46c81-3140-456d-b2a9-690d70aaca35', - integrations: { All: true }, - originalTimestamp: '2021-01-03T17:02:53.193Z', - }, - }, - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 2, userId: 'u1' }, - message: { - type: 'identify', - sentAt: '2021-01-03T17:02:53.195Z', - userId: 'test', - channel: 'web', - context: { - os: { name: '', version: '' }, - app: { - name: 'RudderLabs JavaScript SDK', - build: '1.0.0', - version: '1.1.11', - namespace: 'com.rudderlabs.javascript', - }, - traits: { - firstName: 'Test', - lastName: 'Rudderlabs', - email: 'test@rudderstack.com', - phone: '+12 345 578 900', - userId: 'test', - title: 'Developer', - organization: 'Rudder', - city: 'Tokyo', - region: 'Kanto', - country: 'JP', - zip: '100-0001', - Flagged: false, - Residence: 'Shibuya', - properties: { listId: 'XUepkK', subscribe: true, consent: ['email', 'sms'] }, - }, - locale: 'en-US', - screen: { density: 2 }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.1.11' }, - campaign: {}, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', - }, - rudderId: '8f8fa6b5-8e24-489c-8e22-61f23f2e364f', - messageId: '2116ef8c-efc3-4ca4-851b-02ee60dad6ff', - anonymousId: '97c46c81-3140-456d-b2a9-690d70aaca35', - integrations: { All: true }, - originalTimestamp: '2021-01-03T17:02:53.193Z', - }, - }, - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 3, userId: 'u1' }, - message: { - userId: 'user123', - type: 'group', - groupId: 'XUepkK', - traits: { subscribe: true }, - context: { - traits: { - email: 'test@rudderstack.com', - phone: '+12 345 678 900', - consent: ['email'], - }, - ip: '14.5.67.21', - library: { name: 'http' }, - }, - timestamp: '2020-01-21T00:21:34.208Z', - }, - }, - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 4, userId: 'u1' }, - message: { - userId: 'user123', - type: 'random', - groupId: 'XUepkK', - traits: { subscribe: true }, - context: { - traits: { - email: 'test@rudderstack.com', - phone: '+12 345 678 900', - consent: 'email', - }, - ip: '14.5.67.21', - library: { name: 'http' }, - }, - timestamp: '2020-01-21T00:21:34.208Z', - }, - }, - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 5, userId: 'u1' }, - message: { - userId: 'user123', - type: 'group', - groupId: '', - traits: { subscribe: true }, - context: { - traits: { - email: 'test@rudderstack.com', - phone: '+12 345 678 900', - consent: 'email', - }, - ip: '14.5.67.21', - library: { name: 'http' }, - }, - timestamp: '2020-01-21T00:21:34.208Z', - }, - }, - ], - destType: 'klaviyo', - }, + body: routerRequest, }, }, output: { @@ -263,15 +277,10 @@ export const data = [ files: {}, }, ], - metadata: [ - { jobId: 3, userId: 'u1' }, - { jobId: 2, userId: 'u1' }, - ], + metadata: [generateMetadata(3), generateMetadata(2)], batched: true, statusCode: 200, - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, + destination, }, { batchedRequest: { @@ -315,15 +324,13 @@ export const data = [ }, files: {}, }, - metadata: [{ jobId: 1, userId: 'u1' }], + metadata: [generateMetadata(1)], batched: false, statusCode: 200, - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, + destination, }, { - metadata: [{ jobId: 4, userId: 'u1' }], + metadata: [generateMetadata(4)], batched: false, statusCode: 400, error: 'Event type random is not supported', @@ -334,13 +341,13 @@ export const data = [ feature: 'router', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, + destination, }, { - metadata: [{ jobId: 5, userId: 'u1' }], + metadata: [generateMetadata(5)], batched: false, statusCode: 400, error: 'groupId is a required field for group events', @@ -351,10 +358,10 @@ export const data = [ feature: 'router', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, + destination, }, ], }, diff --git a/test/integrations/destinations/mp/common.ts b/test/integrations/destinations/mp/common.ts index 76ed25a760..82f0e3202b 100644 --- a/test/integrations/destinations/mp/common.ts +++ b/test/integrations/destinations/mp/common.ts @@ -1,8 +1,10 @@ +import { Destination } from '../../../../src/types'; + const defaultMockFns = () => { jest.spyOn(Date, 'now').mockImplementation(() => new Date(Date.UTC(2020, 0, 25)).valueOf()); }; -const sampleDestination = { +const sampleDestination: Destination = { Config: { apiKey: 'dummyApiKey', token: 'dummyApiKey', @@ -13,11 +15,13 @@ const sampleDestination = { DisplayName: 'Mixpanel', ID: '1WhbSZ6uA3H5ChVifHpfL2H6sie', Name: 'MP', + Config: undefined, }, Enabled: true, ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }; const destinationWithSetOnceProperty = { diff --git a/test/integrations/destinations/mp/router/data.ts b/test/integrations/destinations/mp/router/data.ts index 0009e2c438..059e222e92 100644 --- a/test/integrations/destinations/mp/router/data.ts +++ b/test/integrations/destinations/mp/router/data.ts @@ -479,6 +479,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -546,6 +547,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -617,6 +619,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -680,6 +683,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -715,6 +719,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, ], @@ -1197,6 +1202,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -1263,6 +1269,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -1333,6 +1340,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -1396,6 +1404,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -1431,6 +1440,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, ], diff --git a/test/integrations/destinations/salesforce/dataDelivery/data.ts b/test/integrations/destinations/salesforce/dataDelivery/data.ts index 2f1e04815b..cfaa75e23e 100644 --- a/test/integrations/destinations/salesforce/dataDelivery/data.ts +++ b/test/integrations/destinations/salesforce/dataDelivery/data.ts @@ -58,11 +58,6 @@ export const data = [ statusText: 'No Content', }, status: 204, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, }, }, @@ -128,11 +123,6 @@ export const data = [ errorCode: 'INVALID_SESSION_ID', }, ], - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, status: 401, }, statTags: { @@ -210,11 +200,6 @@ export const data = [ }, ], status: 401, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -291,11 +276,6 @@ export const data = [ }, ], status: 403, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -372,11 +352,6 @@ export const data = [ }, ], status: 503, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -451,11 +426,6 @@ export const data = [ error_description: 'authentication failure', }, status: 400, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -526,11 +496,6 @@ export const data = [ errorCode: 'SERVER_UNAVAILABLE', message: 'Server Unavailable', }, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, status: 503, }, message: @@ -619,11 +584,6 @@ export const data = [ ], }, status: 200, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, }, }, @@ -685,11 +645,6 @@ export const data = [ destinationResponse: { response: '[ECONNABORTED] :: Connection aborted', status: 500, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -783,11 +738,6 @@ export const data = [ destinationResponse: { response: '[EAI_AGAIN] :: Temporary failure in name resolution', status: 500, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', diff --git a/test/integrations/destinations/the_trade_desk/common.ts b/test/integrations/destinations/the_trade_desk/common.ts index d792c7faae..28f46df829 100644 --- a/test/integrations/destinations/the_trade_desk/common.ts +++ b/test/integrations/destinations/the_trade_desk/common.ts @@ -1,9 +1,14 @@ +import { Destination } from '../../../../src/types'; + const destType = 'the_trade_desk'; const destTypeInUpperCase = 'THE_TRADE_DESK'; const advertiserId = 'test-advertiser-id'; const dataProviderId = 'rudderstack'; const segmentName = 'test-segment'; -const sampleDestination = { + +const trackerId = 'test-trackerId'; + +const sampleDestination: Destination = { Config: { advertiserId, advertiserSecretKey: 'test-advertiser-secret-key', @@ -11,7 +16,17 @@ const sampleDestination = { ttlInDays: 30, audienceId: segmentName, }, - DestinationDefinition: { Config: { cdkV2Enabled: true } }, + DestinationDefinition: { + Config: { cdkV2Enabled: true }, + ID: '123', + Name: 'TRADEDESK', + DisplayName: 'Trade Desk', + }, + ID: '345', + Name: 'Test', + Enabled: true, + WorkspaceID: '', + Transformations: [], }; const sampleSource = { diff --git a/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts b/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts index 3af7791ec8..9b79a7bcbd 100644 --- a/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts +++ b/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts @@ -1,13 +1,25 @@ +import { Destination } from '../../../../src/types'; + const destType = 'the_trade_desk_real_time_conversions'; const destTypeInUpperCase = 'THE_TRADE_DESK_REAL_TIME_CONVERSIONS'; const advertiserId = 'test-advertiser-id'; const trackerId = 'test-trackerId'; -const sampleDestination = { +const sampleDestination: Destination = { Config: { advertiserId, trackerId, }, - DestinationDefinition: { Config: { cdkV2Enabled: true } }, + Enabled: true, + ID: '123', + Name: 'TRADE_DESK_REAL_TIME_CONVERSIONS', + WorkspaceID: 'test-workspace-id', + Transformations: [], + DestinationDefinition: { + ID: '123', + DisplayName: 'Trade Desk', + Name: 'TRADE_DESK', + Config: { cdkV2Enabled: true }, + }, }; const sampleContextForConversion = { diff --git a/test/integrations/testTypes.ts b/test/integrations/testTypes.ts index 51667e8044..a46277d552 100644 --- a/test/integrations/testTypes.ts +++ b/test/integrations/testTypes.ts @@ -1,5 +1,13 @@ import { AxiosResponse } from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import { + DeliveryV1Response, + ProcessorTransformationRequest, + ProcessorTransformationResponse, + ProxyV1Request, + RouterTransformationRequest, + RouterTransformationResponse, +} from '../../src/types'; export interface requestType { method: string; @@ -31,6 +39,9 @@ export interface mockType { export interface TestCaseData { name: string; description: string; + scenario?: string; + successCriteria?: string; + comment?: string; feature: string; module: string; version?: string; @@ -44,3 +55,76 @@ export type MockHttpCallsData = { httpReq: Record; httpRes: Partial; }; + +export type ProcessorTestData = { + id: string; + name: string; + description: string; + scenario: string; + successCriteria: string; + comment?: string; + feature: string; + module: string; + version: string; + input: { + request: { + body: ProcessorTransformationRequest[]; + }; + }; + output: { + response: { + status: number; + body: ProcessorTransformationResponse[]; + }; + }; +}; +export type RouterTestData = { + id: string; + name: string; + description: string; + comment?: string; + scenario: string; + successCriteria: string; + feature: string; + module: string; + version: string; + input: { + request: { + body: RouterTransformationRequest; + }; + }; + output: { + response: { + status: number; + body: { + output: RouterTransformationResponse[]; + }; + }; + }; +}; + +export type ProxyV1TestData = { + id: string; + name: string; + description: string; + comment?: string; + scenario: string; + successCriteria: string; + feature: string; + module: string; + version: string; + input: { + request: { + body: ProxyV1Request; + method: string; + }; + }; + output: { + response: { + status: number; + body: { + output: DeliveryV1Response; + }; + }; + }; +}; diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index 2cfbe3be8e..2abe4c6d9a 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -1,3 +1,4 @@ +import { z } from 'zod'; import { globSync } from 'glob'; import { join } from 'path'; import { MockHttpCallsData, TestCaseData } from './testTypes'; @@ -5,6 +6,25 @@ import MockAdapter from 'axios-mock-adapter'; import isMatch from 'lodash/isMatch'; import { OptionValues } from 'commander'; import { removeUndefinedAndNullValues } from '@rudderstack/integrations-lib'; +import { + Destination, + Metadata, + ProxyMetdata, + ProxyV0Request, + ProxyV1Request, +} from '../../src/types'; +import { + DeliveryV0ResponseSchema, + DeliveryV0ResponseSchemaForOauth, + DeliveryV1ResponseSchema, + DeliveryV1ResponseSchemaForOauth, + ProcessorTransformationResponseListSchema, + ProcessorTransformationResponseSchema, + ProxyV0RequestSchema, + ProxyV1RequestSchema, + RouterTransformationResponseListSchema, + RouterTransformationResponseSchema, +} from '../../src/types/zodTypes'; const generateAlphanumericId = (size = 36) => [...Array(size)].map(() => ((Math.random() * size) | 0).toString(size)).join(''); @@ -32,7 +52,11 @@ export const getAllTestMockDataFilePaths = (dirPath: string, destination: string const globPattern = join(dirPath, '**', 'network.ts'); let testFilePaths = globSync(globPattern); if (destination) { + const commonTestFilePaths = testFilePaths.filter((testFile) => + testFile.includes('test/integrations/common'), + ); testFilePaths = testFilePaths.filter((testFile) => testFile.includes(destination)); + testFilePaths = [...commonTestFilePaths, ...testFilePaths]; } return testFilePaths; }; @@ -74,13 +98,13 @@ export const addMock = (mock: MockAdapter, axiosMock: MockHttpCallsData) => { break; } }; -export const overrideDestination = (destination, overrideConfigValues) => { +export const overrideDestination = (destination: Destination, overrideConfigValues) => { return Object.assign({}, destination, { Config: { ...destination.Config, ...overrideConfigValues }, }); }; -export const generateIndentifyPayload = (parametersOverride: any) => { +export const generateIndentifyPayload: any = (parametersOverride: any) => { const payload = { type: 'identify', sentAt: parametersOverride.sentAt || '2021-01-03T17:02:53.195Z', @@ -115,7 +139,7 @@ export const generateIndentifyPayload = (parametersOverride: any) => { return removeUndefinedAndNullValues(payload); }; -export const generateSimplifiedIdentifyPayload = (parametersOverride: any) => { +export const generateSimplifiedIdentifyPayload: any = (parametersOverride: any) => { return removeUndefinedAndNullValues({ type: 'identify', sentAt: parametersOverride.sentAt || '2021-01-03T17:02:53.195Z', @@ -133,7 +157,7 @@ export const generateSimplifiedIdentifyPayload = (parametersOverride: any) => { }); }; -export const generateTrackPayload = (parametersOverride: any) => { +export const generateTrackPayload: any = (parametersOverride: any) => { const payload = { type: 'track', sentAt: parametersOverride.sentAt || '2021-01-03T17:02:53.195Z', @@ -169,7 +193,7 @@ export const generateTrackPayload = (parametersOverride: any) => { return removeUndefinedAndNullValues(payload); }; -export const generateSimplifiedTrackPayload = (parametersOverride: any) => { +export const generateSimplifiedTrackPayload: any = (parametersOverride: any) => { return removeUndefinedAndNullValues({ type: 'track', sentAt: parametersOverride.sentAt || '2021-01-03T17:02:53.195Z', @@ -188,7 +212,7 @@ export const generateSimplifiedTrackPayload = (parametersOverride: any) => { }); }; -export const generatePageOrScreenPayload = (parametersOverride: any, eventType: string) => { +export const generatePageOrScreenPayload: any = (parametersOverride: any, eventType: string) => { const payload = { channel: 'web', userId: parametersOverride.userId || 'default-userId', @@ -241,7 +265,7 @@ export const generatePageOrScreenPayload = (parametersOverride: any, eventType: return removeUndefinedAndNullValues(payload); }; -export const generateSimplifiedPageOrScreenPayload = ( +export const generateSimplifiedPageOrScreenPayload: any = ( parametersOverride: any, eventType: string, ) => { @@ -263,7 +287,7 @@ export const generateSimplifiedPageOrScreenPayload = ( }); }; -export const generateGroupPayload = (parametersOverride: any) => { +export const generateGroupPayload: any = (parametersOverride: any) => { const payload = { channel: 'web', context: removeUndefinedAndNullValues({ @@ -306,7 +330,7 @@ export const generateGroupPayload = (parametersOverride: any) => { return removeUndefinedAndNullValues(payload); }; -export const generateSimplifiedGroupPayload = (parametersOverride: any) => { +export const generateSimplifiedGroupPayload: any = (parametersOverride: any) => { return removeUndefinedAndNullValues({ channel: 'web', userId: parametersOverride.userId || 'default-userId', @@ -324,7 +348,7 @@ export const generateSimplifiedGroupPayload = (parametersOverride: any) => { }); }; -export const transformResultBuilder = (matchData) => { +export const transformResultBuilder: any = (matchData) => { return removeUndefinedAndNullValues({ version: '1', type: 'REST', @@ -366,3 +390,146 @@ export const compareObjects = (obj1, obj2, logPrefix = '', differences: string[] return differences; }; + +export const generateProxyV0Payload = ( + payloadParameters: any, + metadataInput?: ProxyMetdata, + destinationConfig?: any, +): ProxyV0Request => { + let metadata: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }; + if (metadataInput) { + metadata = metadataInput; + } + const payload = { + version: 'v0', + type: 'REST', + userId: payloadParameters.userId || 'default-userId', + method: payloadParameters.method || 'POST', + endpoint: payloadParameters.endpoint || '', + headers: payloadParameters.headers || {}, + params: payloadParameters.params || {}, + body: { + JSON: payloadParameters.JSON || {}, + JSON_ARRAY: payloadParameters.JSON_ARRAY || {}, + XML: payloadParameters.XML || {}, + FORM: payloadParameters.FORM || {}, + }, + files: payloadParameters.files || {}, + metadata, + destinationConfig: destinationConfig || {}, + }; + return removeUndefinedAndNullValues(payload) as ProxyV0Request; +}; + +export const generateProxyV1Payload = ( + payloadParameters: any, + metadataInput?: ProxyMetdata[], + destinationConfig?: any, +): ProxyV1Request => { + let metadata: ProxyMetdata[] = [ + { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + ]; + if (metadataInput) { + metadata = metadataInput; + } + const payload = { + version: 'v1', + type: 'REST', + userId: payloadParameters.userId || 'default-userId', + method: payloadParameters.method || 'POST', + endpoint: payloadParameters.endpoint || '', + headers: payloadParameters.headers || {}, + params: payloadParameters.params || {}, + body: { + JSON: payloadParameters.JSON || {}, + JSON_ARRAY: payloadParameters.JSON_ARRAY || {}, + XML: payloadParameters.XML || {}, + FORM: payloadParameters.FORM || {}, + }, + files: payloadParameters.files || {}, + metadata, + destinationConfig: destinationConfig || {}, + }; + return removeUndefinedAndNullValues(payload) as ProxyV1Request; +}; + +// ----------------------------- +// Zod validations + +export const validateTestWithZOD = (testPayload: TestCaseData, response: any) => { + // Validate the resquest payload + switch (testPayload.feature) { + case 'router': + RouterTransformationResponseListSchema.parse(response.body.output); + break; + case 'batch': + RouterTransformationResponseListSchema.parse(response.body); + break; + // case 'user_deletion': + // DeletionSchema.parse(responseBody); + // break; + case 'processor': + ProcessorTransformationResponseListSchema.parse(response.body); + break; + case 'dataDelivery': + if (testPayload.version === 'v0') { + ProxyV0RequestSchema.parse(testPayload.input.request.body); + if (testPayload.scenario === 'Oauth') { + DeliveryV0ResponseSchemaForOauth.parse(response.body.output); + } else { + DeliveryV0ResponseSchema.parse(response.body.output); + } + } else if (testPayload.version === 'v1') { + ProxyV1RequestSchema.parse(testPayload.input.request.body); + if (testPayload.scenario === 'Oauth') { + DeliveryV1ResponseSchemaForOauth.parse(response.body.output); + } else { + DeliveryV1ResponseSchema.parse(response.body.output); + } + } + break; + default: + break; + } + return true; +}; + +// ----------------------------- +// Helper functions + +export const generateMetadata = (jobId: number): any => { + return { + jobId, + attemptNum: 1, + userId: 'default-userId', + sourceId: 'default-sourceId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }; +}; From 6b44fe462db7a3dc3475a0ff763acfa0f4ee8eb8 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Wed, 21 Feb 2024 15:45:36 +0530 Subject: [PATCH 26/33] chore: add user for GH actions (#3114) --- .github/workflows/create-hotfix-branch.yml | 2 +- .github/workflows/draft-new-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-hotfix-branch.yml b/.github/workflows/create-hotfix-branch.yml index ec89f8d342..994283a9f4 100644 --- a/.github/workflows/create-hotfix-branch.yml +++ b/.github/workflows/create-hotfix-branch.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest # Only allow these users to create new hotfix branch from 'main' - if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'koladilip' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'sanpj2292') + if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'koladilip' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'utsabc') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'utsabc') steps: - name: Create Branch uses: peterjgrainger/action-create-branch@v2.4.0 diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index c69a481545..95431fef8e 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest # Only allow release stakeholders to initiate releases - if: (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/')) && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'koladilip' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'yashasvibajpai' || github.actor == 'sanpj2292' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'yashasvibajpai' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'ujjwal-ab') + if: (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/')) && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'koladilip' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'yashasvibajpai' || github.actor == 'sanpj2292' || github.actor == 'utsabc') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'yashasvibajpai' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'utsabc') steps: - name: Checkout uses: actions/checkout@v4.1.1 From 9213de96495c2857a34ef66185d9c60286112da6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 21 Feb 2024 10:28:46 +0000 Subject: [PATCH 27/33] chore(release): 1.56.1 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d67c45a147..902d797301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [1.56.1](https://github.com/rudderlabs/rudder-transformer/compare/v1.56.0...v1.56.1) (2024-02-21) + + +### Bug Fixes + +* update proxy data type for response handler input ([#3030](https://github.com/rudderlabs/rudder-transformer/issues/3030)) ([457a18b](https://github.com/rudderlabs/rudder-transformer/commit/457a18b2aec03aa0dfafcadc611b5f7176e97beb)) + ## [1.56.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.55.0...v1.56.0) (2024-02-19) diff --git a/package-lock.json b/package-lock.json index 2a51a2b916..2d4f32bd5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rudder-transformer", - "version": "1.56.0", + "version": "1.56.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rudder-transformer", - "version": "1.56.0", + "version": "1.56.1", "license": "ISC", "dependencies": { "@amplitude/ua-parser-js": "0.7.24", diff --git a/package.json b/package.json index bd7c3619fb..a1053c0496 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rudder-transformer", - "version": "1.56.0", + "version": "1.56.1", "description": "", "homepage": "https://github.com/rudderlabs/rudder-transformer#readme", "bugs": { From c844aaf972059a71a7e35063d52d7256cb29dbcf Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Thu, 22 Feb 2024 22:49:42 +0530 Subject: [PATCH 28/33] chore: add requestMethod and module latency labels (#3085) * chore: add requestMethod and module label x1 * chore: add requestMethod and module label x2 * chore: add requestMethod and module latency labels (#3097) * chore: added statuscode stat for user deletion (#3108) * fix: component tests --------- Co-authored-by: Mihir Bhalala <77438541+mihir-4116@users.noreply.github.com> Co-authored-by: mihir-4116 --- src/adapters/network.js | 6 +++ src/cdk/v2/destinations/intercom/utils.js | 38 ++++++++++++++----- src/util/prometheus.js | 4 +- .../destinations/active_campaign/transform.js | 12 ++++++ src/v0/destinations/af/deleteUsers.js | 3 ++ src/v0/destinations/am/deleteUsers.js | 3 ++ src/v0/destinations/braze/braze.util.test.js | 9 ++++- src/v0/destinations/braze/deleteUsers.js | 5 ++- src/v0/destinations/braze/transform.js | 3 ++ src/v0/destinations/braze/util.js | 3 ++ src/v0/destinations/canny/util.js | 2 + src/v0/destinations/clevertap/deleteUsers.js | 3 ++ src/v0/destinations/clickup/util.js | 3 ++ src/v0/destinations/custify/deleteUsers.js | 3 ++ src/v0/destinations/custify/util.js | 2 + src/v0/destinations/delighted/util.js | 8 +++- src/v0/destinations/drip/util.js | 16 +++++++- src/v0/destinations/engage/deleteUsers.js | 4 ++ src/v0/destinations/freshmarketer/utils.js | 14 +++++++ src/v0/destinations/freshsales/utils.js | 10 +++++ src/v0/destinations/ga/deleteUsers.js | 2 + src/v0/destinations/gainsight/util.js | 24 ++++++++++-- src/v0/destinations/gainsight_px/util.js | 24 ++++++++++-- .../networkHandler.js | 4 ++ .../networkHandler.js | 10 +++++ .../utils.js | 2 + .../networkHandler.js | 6 +++ src/v0/destinations/hs/util.js | 10 +++++ src/v0/destinations/intercom/deleteUsers.js | 3 ++ src/v0/destinations/iterable/deleteUsers.js | 4 ++ src/v0/destinations/klaviyo/util.js | 2 + src/v0/destinations/kustomer/util.js | 8 +++- src/v0/destinations/mailchimp/utils.js | 16 +++++++- src/v0/destinations/marketo/util.js | 4 ++ .../marketo_bulk_upload/fetchJobStatus.js | 3 ++ .../marketo_bulk_upload/fileUpload.js | 3 ++ .../marketo_bulk_upload.util.test.js | 4 ++ .../destinations/marketo_bulk_upload/poll.js | 3 ++ .../destinations/marketo_bulk_upload/util.js | 6 +++ src/v0/destinations/mautic/utils.js | 3 ++ src/v0/destinations/monday/util.js | 2 + src/v0/destinations/mp/deleteUsers.js | 5 +++ src/v0/destinations/profitwell/utils.js | 3 ++ src/v0/destinations/rakuten/networkHandler.js | 8 +++- src/v0/destinations/salesforce/transform.js | 6 +++ src/v0/destinations/salesforce/utils.js | 3 ++ src/v0/destinations/sendgrid/deleteUsers.js | 4 ++ src/v0/destinations/sendgrid/util.js | 3 ++ src/v0/destinations/sendinblue/util.js | 3 ++ src/v0/destinations/sfmc/transform.js | 8 +++- .../networkHandler.js | 3 ++ src/v0/destinations/sprig/deleteUsers.js | 5 ++- .../the_trade_desk/networkHandler.js | 8 +++- src/v0/destinations/trengo/transform.js | 8 +++- src/v0/destinations/user/utils.js | 14 +++++++ src/v0/destinations/wootric/util.js | 4 ++ src/v0/destinations/yahoo_dsp/util.js | 3 ++ src/v0/destinations/zendesk/transform.js | 31 ++++++++++++++- src/v0/util/tags.js | 3 +- 59 files changed, 383 insertions(+), 33 deletions(-) diff --git a/src/adapters/network.js b/src/adapters/network.js index b0bd14374e..d759412b7a 100644 --- a/src/adapters/network.js +++ b/src/adapters/network.js @@ -49,11 +49,15 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => { const destType = statTags.destType ? statTags.destType : ''; const feature = statTags.feature ? statTags.feature : ''; const endpointPath = statTags.endpointPath ? statTags.endpointPath : ''; + const requestMethod = statTags.requestMethod ? statTags.requestMethod : ''; + const module = statTags.module ? statTags.module : ''; const statusCode = clientResponse.success ? clientResponse.response.status : ''; stats.timing('outgoing_request_latency', startTime, { feature, destType, endpointPath, + requestMethod, + module }); stats.counter('outgoing_request_count', 1, { feature, @@ -61,6 +65,8 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => { endpointPath, success: clientResponse.success, statusCode, + requestMethod, + module }); }; diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js index 0f18029f19..ba3063c9f9 100644 --- a/src/cdk/v2/destinations/intercom/utils.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -246,11 +246,20 @@ const searchContact = async (message, destination) => { const headers = getHeaders(destination); const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${SEARCH_CONTACT_ENDPOINT}`; - const response = await httpPOST(endpoint, data, { - headers, - destType: 'intercom', - feature: 'transformation', - }); + const response = await httpPOST( + endpoint, + data, + { + headers, + }, + { + destType: 'intercom', + feature: 'transformation', + endpointPath: '/contacts/search', + requestMethod: 'POST', + module: 'router', + }, + ); const processedUserResponse = processAxiosResponse(response); if (isHttpStatusSuccess(processedUserResponse.status)) { return processedUserResponse.response?.data.length > 0 @@ -280,11 +289,20 @@ const createOrUpdateCompany = async (payload, destination) => { const finalPayload = JSON.stringify(removeUndefinedAndNullValues(payload)); const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}`; - const response = await httpPOST(endpoint, finalPayload, { - headers, - destType: 'intercom', - feature: 'transformation', - }); + const response = await httpPOST( + endpoint, + finalPayload, + { + headers, + }, + { + destType: 'intercom', + feature: 'transformation', + endpointPath: '/companies', + requestMethod: 'POST', + module: 'router', + }, + ); const processedResponse = processAxiosResponse(response); if (isHttpStatusSuccess(processedResponse.status)) { diff --git a/src/util/prometheus.js b/src/util/prometheus.js index d7ba3b7c61..48868449c3 100644 --- a/src/util/prometheus.js +++ b/src/util/prometheus.js @@ -533,7 +533,7 @@ class Prometheus { name: 'outgoing_request_count', help: 'Outgoing HTTP requests count', type: 'counter', - labelNames: ['feature', 'destType', 'endpointPath', 'success', 'statusCode'], + labelNames: ['feature', 'destType', 'endpointPath', 'success', 'statusCode', 'requestMethod' , 'module'], }, // Gauges @@ -573,7 +573,7 @@ class Prometheus { name: 'outgoing_request_latency', help: 'Outgoing HTTP requests duration in seconds', type: 'histogram', - labelNames: ['feature', 'destType', 'endpointPath'], + labelNames: ['feature', 'destType', 'endpointPath', 'requestMethod', 'module'], }, { name: 'http_request_duration', diff --git a/src/v0/destinations/active_campaign/transform.js b/src/v0/destinations/active_campaign/transform.js index 981dbd7520..3978f868b1 100644 --- a/src/v0/destinations/active_campaign/transform.js +++ b/src/v0/destinations/active_campaign/transform.js @@ -62,6 +62,8 @@ const syncContact = async (contactPayload, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: endPoint, + requestMethod: 'POST', + module: 'router', }); if (res.success === false) { errorHandler(res, 'Failed to create new contact'); @@ -129,6 +131,8 @@ const customTagProcessor = async (message, category, destination, contactId) => destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/tags`, + requestMethod: 'GET', + module: 'router', }); promises.push(resp); } @@ -253,6 +257,8 @@ const customFieldProcessor = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/fields`, + requestMethod: 'GET', + module: 'router', }); promises.push(resp); } @@ -351,6 +357,8 @@ const customListProcessor = async (message, category, destination, contactId) => destType: 'active_campaign', feature: 'transformation', endpointPath: mergeListWithContactUrl, + requestMethod: 'POST', + module: 'router', }); promises.push(res); } @@ -409,6 +417,8 @@ const screenRequestHandler = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/eventTrackingEvents`, + requestMethod: 'GET', + module: 'router', }); if (res.success === false) { errorHandler(res, 'Failed to retrieve events'); @@ -473,6 +483,8 @@ const trackRequestHandler = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/eventTrackingEvents`, + requestMethod: 'GET', + module: 'router', }); if (res.success === false) { diff --git a/src/v0/destinations/af/deleteUsers.js b/src/v0/destinations/af/deleteUsers.js index bb711292c0..ab515642aa 100644 --- a/src/v0/destinations/af/deleteUsers.js +++ b/src/v0/destinations/af/deleteUsers.js @@ -39,6 +39,8 @@ const deleteUser = async (config, endpoint, body, identityType, identityValue) = destType: 'af', feature: 'deleteUsers', endpointPath: `appsflyer.com/api/gdpr/v1/opendsr_requests`, + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(response); @@ -48,6 +50,7 @@ const deleteUser = async (config, endpoint, body, identityType, identityValue) = handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/am/deleteUsers.js b/src/v0/destinations/am/deleteUsers.js index 6de9cf64a1..96c4f7b19c 100644 --- a/src/v0/destinations/am/deleteUsers.js +++ b/src/v0/destinations/am/deleteUsers.js @@ -43,6 +43,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'am', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status)) { @@ -51,6 +53,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/braze/braze.util.test.js b/src/v0/destinations/braze/braze.util.test.js index 9e82a235f1..7b6a93d359 100644 --- a/src/v0/destinations/braze/braze.util.test.js +++ b/src/v0/destinations/braze/braze.util.test.js @@ -305,7 +305,14 @@ describe('dedup utility tests', () => { }, timeout: 10000, }, - { destType: 'braze', feature: 'transformation' }, + { + destType: 'braze', + feature: 'transformation', + endpointPath: '/users/export/ids', + feature: 'transformation', + module: 'router', + requestMethod: 'POST', + }, ); }); diff --git a/src/v0/destinations/braze/deleteUsers.js b/src/v0/destinations/braze/deleteUsers.js index b94d901138..33c0f2ef7f 100644 --- a/src/v0/destinations/braze/deleteUsers.js +++ b/src/v0/destinations/braze/deleteUsers.js @@ -22,7 +22,7 @@ const userDeletionHandler = async (userAttributes, config) => { // Endpoints different for different data centers. // DOC: https://www.braze.com/docs/user_guide/administrative/access_braze/braze_instances/ let endPoint; - const endpointPath = '/users/delete'; // TODO: to handle for destinations dynamically by extracting from endpoint + const endpointPath = '/users/delete'; const dataCenterArr = dataCenter.trim().split('-'); if (dataCenterArr[0].toLowerCase() === 'eu') { endPoint = 'https://rest.fra-01.braze.eu/users/delete'; @@ -46,6 +46,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'braze', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -54,6 +56,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/braze/transform.js b/src/v0/destinations/braze/transform.js index 6549f5658f..d45640272e 100644 --- a/src/v0/destinations/braze/transform.js +++ b/src/v0/destinations/braze/transform.js @@ -223,6 +223,9 @@ async function processIdentify(message, destination) { { destType: 'braze', feature: 'transformation', + requestMethod: 'POST', + module: 'router', + endpointPath: '/users/identify', }, ); if (!isHttpStatusSuccess(brazeIdentifyResp.status)) { diff --git a/src/v0/destinations/braze/util.js b/src/v0/destinations/braze/util.js index 40b9a7eada..5f1f1e6205 100644 --- a/src/v0/destinations/braze/util.js +++ b/src/v0/destinations/braze/util.js @@ -163,6 +163,9 @@ const BrazeDedupUtility = { { destType: 'braze', feature: 'transformation', + requestMethod: 'POST', + module: 'router', + endpointPath: '/users/export/ids', }, ); stats.counter('braze_lookup_failure_count', 1, { diff --git a/src/v0/destinations/canny/util.js b/src/v0/destinations/canny/util.js index f514a01e5c..1d03eed4b9 100644 --- a/src/v0/destinations/canny/util.js +++ b/src/v0/destinations/canny/util.js @@ -46,6 +46,8 @@ const retrieveUserId = async (apiKey, message) => { destType: 'canny', feature: 'transformation', endpointPath: `/v1/users/retrieve`, + requestMethod: 'POST', + module: 'processor', }, ); logger.debug(response); diff --git a/src/v0/destinations/clevertap/deleteUsers.js b/src/v0/destinations/clevertap/deleteUsers.js index 3c07a63d93..52119bf0f1 100644 --- a/src/v0/destinations/clevertap/deleteUsers.js +++ b/src/v0/destinations/clevertap/deleteUsers.js @@ -53,6 +53,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'clevertap', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -62,6 +64,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/clickup/util.js b/src/v0/destinations/clickup/util.js index 148fe1bd07..74e961906c 100644 --- a/src/v0/destinations/clickup/util.js +++ b/src/v0/destinations/clickup/util.js @@ -217,6 +217,9 @@ const retrieveCustomFields = async (listId, apiToken) => { const customFieldsResponse = await httpGET(endpoint, requestOptions, { destType: 'clickup', feature: 'transformation', + endpointPath: '/list/listId/field', + requestMethod: 'GET', + module: 'router', }); const processedCustomFieldsResponse = processAxiosResponse(customFieldsResponse); diff --git a/src/v0/destinations/custify/deleteUsers.js b/src/v0/destinations/custify/deleteUsers.js index 921cf953bd..690768a170 100644 --- a/src/v0/destinations/custify/deleteUsers.js +++ b/src/v0/destinations/custify/deleteUsers.js @@ -38,6 +38,9 @@ const userDeletionHandler = async (userAttributes, config) => { const deletionResponse = await httpDELETE(requestUrl, requestOptions, { destType: 'custify', feature: 'deleteUsers', + requestMethod: 'DELETE', + endpointPath: '/people', + module: 'deletion', }); const processedDeletionRequest = processAxiosResponse(deletionResponse); if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) { diff --git a/src/v0/destinations/custify/util.js b/src/v0/destinations/custify/util.js index 8ecabccd2e..b6f3446503 100644 --- a/src/v0/destinations/custify/util.js +++ b/src/v0/destinations/custify/util.js @@ -41,6 +41,8 @@ const createUpdateCompany = async (companyPayload, Config) => { destType: 'custify', feature: 'transformation', endpointPath: `/company`, + requestMethod: 'POST', + module: 'router', }, ); const processedCompanyResponse = processAxiosResponse(companyResponse); diff --git a/src/v0/destinations/delighted/util.js b/src/v0/destinations/delighted/util.js index 2c92685fd7..c690bf5f5c 100644 --- a/src/v0/destinations/delighted/util.js +++ b/src/v0/destinations/delighted/util.js @@ -61,7 +61,13 @@ const userValidity = async (channel, Config, userId) => { }, params: paramsdata, }, - { destType: 'delighted', feature: 'transformation' }, + { + destType: 'delighted', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/people.json', + module: 'router', + }, ); if (response && response.data && response.status === 200 && Array.isArray(response.data)) { return response.data.length > 0; diff --git a/src/v0/destinations/drip/util.js b/src/v0/destinations/drip/util.js index a502cf0d20..b7015c9351 100644 --- a/src/v0/destinations/drip/util.js +++ b/src/v0/destinations/drip/util.js @@ -31,7 +31,13 @@ const userExists = async (Config, id) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'drip', feature: 'transformation' }, + { + destType: 'drip', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/subscribers/id', + module: 'router', + }, ); if (response && response.status) { return response.status === 200; @@ -70,7 +76,13 @@ const createUpdateUser = async (finalpayload, Config, basicAuth) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'drip', feature: 'transformation' }, + { + destType: 'drip', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/subscribers', + module: 'router', + }, ); if (response) { return response.status === 200 || response.status === 201; diff --git a/src/v0/destinations/engage/deleteUsers.js b/src/v0/destinations/engage/deleteUsers.js index a3c3055c7d..3616d2408d 100644 --- a/src/v0/destinations/engage/deleteUsers.js +++ b/src/v0/destinations/engage/deleteUsers.js @@ -42,6 +42,9 @@ const userDeletionHandler = async (userAttributes, config) => { { destType: 'engage', feature: 'deleteUsers', + requestMethod: 'DELETE', + endpointPath: '/users/userId', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(response); @@ -51,6 +54,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/freshmarketer/utils.js b/src/v0/destinations/freshmarketer/utils.js index 5e3ba6e67e..c80711ff8d 100644 --- a/src/v0/destinations/freshmarketer/utils.js +++ b/src/v0/destinations/freshmarketer/utils.js @@ -50,6 +50,8 @@ const createUpdateAccount = async (payload, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/sales_accounts/upsert`, + requestMethod: 'POST', + module: 'router', }); accountResponse = processAxiosResponse(accountResponse); if (accountResponse.status !== 200 && accountResponse.status !== 201) { @@ -95,6 +97,8 @@ const getUserAccountDetails = async (payload, userEmail, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `crm/sales/api/contacts/upsert?include=sales_accounts`, + requestMethod: 'POST', + module: 'router', }); userSalesAccountResponse = processAxiosResponse(userSalesAccountResponse); if (userSalesAccountResponse.status !== 200 && userSalesAccountResponse.status !== 201) { @@ -145,6 +149,8 @@ const createOrUpdateListDetails = async (listName, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/lists`, + requestMethod: 'GET', + module: 'router', }); listResponse = processAxiosResponse(listResponse); if (listResponse.status !== 200) { @@ -165,6 +171,8 @@ const createOrUpdateListDetails = async (listName, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/lists`, + requestMethod: 'POST', + module: 'router', }); listResponse = processAxiosResponse(listResponse); if (listResponse.status !== 200) { @@ -240,6 +248,8 @@ const getContactsDetails = async (userEmail, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert`, + requestMethod: 'POST', + module: 'router', }); userResponse = processAxiosResponse(userResponse); if (userResponse.status !== 200 && userResponse.status !== 201) { @@ -314,6 +324,8 @@ const UpdateContactWithLifeCycleStage = async (message, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/selector/lifecycle_stages`, + requestMethod: 'GET', + module: 'router', }); lifeCycleStagesResponse = processAxiosResponse(lifeCycleStagesResponse); if (lifeCycleStagesResponse.status !== 200) { @@ -400,6 +412,8 @@ const UpdateContactWithSalesActivity = async (payload, message, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/selector/sales_activity_types`, + requestMethod: 'GET', + module: 'router', }); salesActivityResponse = processAxiosResponse(salesActivityResponse); if (salesActivityResponse.status !== 200) { diff --git a/src/v0/destinations/freshsales/utils.js b/src/v0/destinations/freshsales/utils.js index 5008fedc2d..977bde0abb 100644 --- a/src/v0/destinations/freshsales/utils.js +++ b/src/v0/destinations/freshsales/utils.js @@ -48,6 +48,8 @@ const createUpdateAccount = async (payload, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/sales_accounts/upsert`, + requestMethod: 'POST', + module: 'router', }); accountResponse = processAxiosResponse(accountResponse); if (accountResponse.status !== 200 && accountResponse.status !== 201) { @@ -92,6 +94,8 @@ const getUserAccountDetails = async (payload, userEmail, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert?include=sales_accounts`, + requestMethod: 'POST', + module: 'router', }); userSalesAccountResponse = processAxiosResponse(userSalesAccountResponse); if (userSalesAccountResponse.status !== 200 && userSalesAccountResponse.status !== 201) { @@ -148,6 +152,8 @@ const getContactsDetails = async (userEmail, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert`, + requestMethod: 'POST', + module: 'router', }); userResponse = processAxiosResponse(userResponse); if (userResponse.status !== 200 && userResponse.status !== 201) { @@ -239,6 +245,8 @@ const UpdateContactWithSalesActivity = async (payload, message, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/sales_activity_types`, + requestMethod: 'GET', + module: 'router', }); salesActivityResponse = processAxiosResponse(salesActivityResponse); if (salesActivityResponse.status !== 200) { @@ -319,6 +327,8 @@ const UpdateContactWithLifeCycleStage = async (message, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/lifecycle_stages`, + requestMethod: 'GET', + module: 'router', }); lifeCycleStagesResponse = processAxiosResponse(lifeCycleStagesResponse); if (lifeCycleStagesResponse.status !== 200) { diff --git a/src/v0/destinations/ga/deleteUsers.js b/src/v0/destinations/ga/deleteUsers.js index 06e674048a..524e2c14b4 100644 --- a/src/v0/destinations/ga/deleteUsers.js +++ b/src/v0/destinations/ga/deleteUsers.js @@ -81,6 +81,8 @@ const userDeletionHandler = async (userAttributes, config, rudderDestInfo) => { destType: 'ga', feature: 'deleteUsers', endpointPath: '/userDeletion/userDeletionRequests:upsert', + requestMethod: 'POST', + module: 'deletion', }, ); // process the response to know about refreshing scenario diff --git a/src/v0/destinations/gainsight/util.js b/src/v0/destinations/gainsight/util.js index 39e666c1a5..4c7fd58193 100644 --- a/src/v0/destinations/gainsight/util.js +++ b/src/v0/destinations/gainsight/util.js @@ -22,7 +22,13 @@ const searchGroup = async (groupName, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/data/objects/query/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; @@ -56,7 +62,13 @@ const createGroup = async (payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/data/objects/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; @@ -93,7 +105,13 @@ const updateGroup = async (payload, Config) => { keys: 'Name', }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'PUT', + endpointPath: '/data/objects/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; diff --git a/src/v0/destinations/gainsight_px/util.js b/src/v0/destinations/gainsight_px/util.js index 5109286b3f..e03fbbf148 100644 --- a/src/v0/destinations/gainsight_px/util.js +++ b/src/v0/destinations/gainsight_px/util.js @@ -56,7 +56,13 @@ const objectExists = async (id, Config, objectType) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/accounts/accountId', + module: 'router', + }, ); if (response && response.status === 200) { return { success: true, err: null }; @@ -88,7 +94,13 @@ const createAccount = async (payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/accounts', + module: 'router', + }, ); if (response && response.status === 201) { return { success: true, err: null }; @@ -121,7 +133,13 @@ const updateAccount = async (accountId, payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'PUT', + endpointPath: '/accounts/accountId', + module: 'router', + }, ); if (response && response.status === 204) { return { success: true, err: null }; diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js index b4590fb71c..3ea985e773 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js @@ -45,6 +45,8 @@ const getConversionActionId = async (method, headers, params) => { destType: 'google_adwords_enhanced_conversions', feature: 'proxy', endpointPath: `/googleAds:searchStream`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); if (!isHttpStatusSuccess(gaecConversionActionIdResponse.status)) { @@ -98,6 +100,8 @@ const ProxyRequest = async (request) => { destType: 'google_adwords_enhanced_conversions', feature: 'proxy', endpointPath: `/googleAds:uploadOfflineUserData`, + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js index 318b7802df..5541fd6e1e 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js @@ -34,6 +34,8 @@ const createJob = async (endpoint, headers, payload) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/create`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); createJobResponse = processAxiosResponse(createJobResponse); @@ -59,6 +61,8 @@ const addConversionToJob = async (endpoint, headers, jobId, payload) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/addOperations`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); addConversionToJobResponse = processAxiosResponse(addConversionToJobResponse); @@ -83,6 +87,8 @@ const runTheJob = async (endpoint, headers, payload, jobId) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/run`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); return executeJobResponse; @@ -110,6 +116,8 @@ const getConversionCustomVariable = async (headers, params) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/searchStream`, + requestMethod: 'POST', + module: 'dataDelivery', }); searchStreamResponse = processAxiosResponse(searchStreamResponse); if (!isHttpStatusSuccess(searchStreamResponse.status)) { @@ -247,6 +255,8 @@ const ProxyRequest = async (request) => { feature: 'proxy', destType: 'gogole_adwords_offline_conversions', endpointPath: `/proxy`, + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 599a163c54..19989d0eaa 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -64,6 +64,8 @@ const getConversionActionId = async (headers, params) => { destType: 'google_adwords_offline_conversions', feature: 'transformation', endpointPath: `/googleAds:searchStream`, + requestMethod: 'POST', + module: 'dataDelivery' }); searchStreamResponse = processAxiosResponse(searchStreamResponse); if (!isHttpStatusSuccess(searchStreamResponse.status)) { diff --git a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js index dbd055f1a1..3045c1713f 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js @@ -41,6 +41,8 @@ const createJob = async (endpoint, headers, method, params) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/customers/create', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; @@ -65,6 +67,8 @@ const addUserToJob = async (endpoint, headers, method, jobId, body) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/addOperations', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; @@ -87,6 +91,8 @@ const runTheJob = async (endpoint, headers, method, jobId) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/run', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/hs/util.js b/src/v0/destinations/hs/util.js index e905ee63c4..32ee923f5f 100644 --- a/src/v0/destinations/hs/util.js +++ b/src/v0/destinations/hs/util.js @@ -104,6 +104,8 @@ const getProperties = async (destination) => { destType: 'hs', feature: 'transformation', endpointPath: `/properties/v1/contacts/properties`, + requestMethod: 'GET', + module: 'router', }); hubspotPropertyMapResponse = processAxiosResponse(hubspotPropertyMapResponse); } else { @@ -116,6 +118,8 @@ const getProperties = async (destination) => { destType: 'hs', feature: 'transformation', endpointPath: `/properties/v1/contacts/properties?hapikey`, + requestMethod: 'GET', + module: 'router', }, ); hubspotPropertyMapResponse = processAxiosResponse(hubspotPropertyMapResponse); @@ -365,6 +369,8 @@ const searchContacts = async (message, destination) => { destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }, ); searchContactsResponse = processAxiosResponse(searchContactsResponse); @@ -375,6 +381,8 @@ const searchContacts = async (message, destination) => { destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }); searchContactsResponse = processAxiosResponse(searchContactsResponse); } @@ -539,6 +547,8 @@ const performHubSpotSearch = async ( destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }); const processedResponse = processAxiosResponse(searchResponse); diff --git a/src/v0/destinations/intercom/deleteUsers.js b/src/v0/destinations/intercom/deleteUsers.js index b91f520ade..2c35f29e53 100644 --- a/src/v0/destinations/intercom/deleteUsers.js +++ b/src/v0/destinations/intercom/deleteUsers.js @@ -39,6 +39,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'intercom', feature: 'deleteUsers', endpointPath: '/user_delete_requests', + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -47,6 +49,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/iterable/deleteUsers.js b/src/v0/destinations/iterable/deleteUsers.js index a179a8930f..015a9de9a0 100644 --- a/src/v0/destinations/iterable/deleteUsers.js +++ b/src/v0/destinations/iterable/deleteUsers.js @@ -36,6 +36,9 @@ const userDeletionHandler = async (userAttributes, config) => { const resp = await httpDELETE(url, requestOptions, { destType: 'iterable', feature: 'deleteUsers', + endpointPath: '/users/byUserId/uId', + requestMethod: 'DELETE', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -46,6 +49,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/klaviyo/util.js b/src/v0/destinations/klaviyo/util.js index 60b334f3a2..df2dbb4712 100644 --- a/src/v0/destinations/klaviyo/util.js +++ b/src/v0/destinations/klaviyo/util.js @@ -45,6 +45,8 @@ const getIdFromNewOrExistingProfile = async (endpoint, payload, requestOptions) destType: 'klaviyo', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }, ); diff --git a/src/v0/destinations/kustomer/util.js b/src/v0/destinations/kustomer/util.js index 571a03f139..530983bb26 100644 --- a/src/v0/destinations/kustomer/util.js +++ b/src/v0/destinations/kustomer/util.js @@ -139,7 +139,13 @@ const fetchKustomer = async (url, destination) => { Authorization: `Bearer ${destination.Config.apiKey}`, }, }, - { destType: 'kustomer', feature: 'transformation' }, + { + destType: 'kustomer', + feature: 'transformation', + endpointPath: '/customers/email', + requestMethod: 'GET', + module: 'processor', + }, ); } catch (err) { if (err.response) { diff --git a/src/v0/destinations/mailchimp/utils.js b/src/v0/destinations/mailchimp/utils.js index e1e2e9883b..1f4fc03ee5 100644 --- a/src/v0/destinations/mailchimp/utils.js +++ b/src/v0/destinations/mailchimp/utils.js @@ -163,7 +163,13 @@ const checkIfMailExists = async (apiKey, datacenterId, audienceId, email) => { Authorization: `Basic ${basicAuth}`, }, }, - { destType: 'mailchimp', feature: 'transformation' }, + { + destType: 'mailchimp', + feature: 'transformation', + endpointPath: '/lists/audienceId/members/email', + requestMethod: 'GET', + module: 'router', + }, ); if (response?.data?.contact_id) { userStatus.exists = true; @@ -194,7 +200,13 @@ const checkIfDoubleOptIn = async (apiKey, datacenterId, audienceId) => { Authorization: `Basic ${basicAuth}`, }, }, - { destType: 'mailchimp', feature: 'transformation' }, + { + destType: 'mailchimp', + feature: 'transformation', + endpointPath: '/lists/audienceId', + requestMethod: 'GET', + module: 'router', + }, ); } catch (error) { const status = error.status || 400; diff --git a/src/v0/destinations/marketo/util.js b/src/v0/destinations/marketo/util.js index 54ff70708a..b3a24fb411 100644 --- a/src/v0/destinations/marketo/util.js +++ b/src/v0/destinations/marketo/util.js @@ -248,6 +248,8 @@ const sendGetRequest = async (url, options) => { destType: 'marketo', feature: 'transformation', endpointPath: `/v1/leads`, + requestMethod: 'GET', + module: 'router', }); const processedResponse = processAxiosResponse(clientResponse); return processedResponse; @@ -264,6 +266,8 @@ const sendPostRequest = async (url, data, options) => { destType: 'marketo', feature: 'transformation', endpointPath: `/v1/leads`, + requestMethod: 'POST', + module: 'router', }); const processedResponse = processAxiosResponse(clientResponse); return processedResponse; diff --git a/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js b/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js index e6f5662000..db3b13eeb8 100644 --- a/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js +++ b/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js @@ -33,6 +33,9 @@ const getJobsStatus = async (event, type, accessToken) => { const { processedResponse: resp } = await handleHttpRequest('get', url, requestOptions, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/batch/', + requestMethod: 'GET', + module: 'router', }); const endTime = Date.now(); const requestTime = endTime - startTime; diff --git a/src/v0/destinations/marketo_bulk_upload/fileUpload.js b/src/v0/destinations/marketo_bulk_upload/fileUpload.js index 9c42fdc98d..b49a265fd5 100644 --- a/src/v0/destinations/marketo_bulk_upload/fileUpload.js +++ b/src/v0/destinations/marketo_bulk_upload/fileUpload.js @@ -198,6 +198,9 @@ const getImportID = async (input, config, accessToken, csvHeader) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads.json', + requestMethod: 'POST', + module: 'router', }, ); const endTime = Date.now(); diff --git a/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js b/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js index 875b0d8280..aa4b3aacc4 100644 --- a/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js +++ b/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js @@ -296,6 +296,10 @@ describe('getAccessToken', () => { expect(handleHttpRequest).toHaveBeenCalledWith('get', url, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/identity/oauth/token', + feature: 'transformation', + module: 'router', + requestMethod: 'GET', }); }); diff --git a/src/v0/destinations/marketo_bulk_upload/poll.js b/src/v0/destinations/marketo_bulk_upload/poll.js index db7a634774..f53347d6e5 100644 --- a/src/v0/destinations/marketo_bulk_upload/poll.js +++ b/src/v0/destinations/marketo_bulk_upload/poll.js @@ -26,6 +26,9 @@ const getPollStatus = async (event) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/batch/importId.json', + requestMethod: 'GET', + module: 'router', }, ); if (!isHttpStatusSuccess(pollStatus.status)) { diff --git a/src/v0/destinations/marketo_bulk_upload/util.js b/src/v0/destinations/marketo_bulk_upload/util.js index fac04af431..4c99ba7483 100644 --- a/src/v0/destinations/marketo_bulk_upload/util.js +++ b/src/v0/destinations/marketo_bulk_upload/util.js @@ -128,6 +128,9 @@ const getAccessToken = async (config) => { const { processedResponse: accessTokenResponse } = await handleHttpRequest('get', url, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/identity/oauth/token', + requestMethod: 'GET', + module: 'router', }); // sample response : {response: '[ENOTFOUND] :: DNS lookup failed', status: 400} @@ -352,6 +355,9 @@ const getFieldSchemaMap = async (accessToken, munchkinId) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/describe2.json', + requestMethod: 'GET', + module: 'router', }, ); diff --git a/src/v0/destinations/mautic/utils.js b/src/v0/destinations/mautic/utils.js index d8ad8dbffc..7a1827e769 100644 --- a/src/v0/destinations/mautic/utils.js +++ b/src/v0/destinations/mautic/utils.js @@ -183,6 +183,9 @@ const searchContactIds = async (message, Config, baseUrl) => { { destType: 'mautic', feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', }, ); searchContactsResponse = processAxiosResponse(searchContactsResponse); diff --git a/src/v0/destinations/monday/util.js b/src/v0/destinations/monday/util.js index 736f0133fd..872fad42a7 100644 --- a/src/v0/destinations/monday/util.js +++ b/src/v0/destinations/monday/util.js @@ -195,6 +195,8 @@ const getBoardDetails = async (url, boardID, apiToken) => { destType: 'monday', feature: 'transformation', endpointPath: '/v2', + requestMethod: 'POST', + module: 'router', }, ); const boardDetailsResponse = processAxiosResponse(clientResponse); diff --git a/src/v0/destinations/mp/deleteUsers.js b/src/v0/destinations/mp/deleteUsers.js index f01475ef2b..e1240c609d 100644 --- a/src/v0/destinations/mp/deleteUsers.js +++ b/src/v0/destinations/mp/deleteUsers.js @@ -49,6 +49,8 @@ const deleteProfile = async (userAttributes, config) => { destType: 'mp', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); if (!isHttpStatusSuccess(handledDelResponse.status)) { @@ -57,6 +59,7 @@ const deleteProfile = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); @@ -104,6 +107,8 @@ const createDeletionTask = async (userAttributes, config) => { destType: 'mp', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); if (!isHttpStatusSuccess(handledDelResponse.status)) { diff --git a/src/v0/destinations/profitwell/utils.js b/src/v0/destinations/profitwell/utils.js index acc4db2035..1b23561721 100644 --- a/src/v0/destinations/profitwell/utils.js +++ b/src/v0/destinations/profitwell/utils.js @@ -188,6 +188,9 @@ const getSubscriptionHistory = async (endpoint, options) => { const res = await httpGET(endpoint, requestOptions, { destType: 'profitwell', feature: 'transformation', + endpointPath: '/users/userId', + requestMethod: 'GET', + module: 'router', }); return res; }; diff --git a/src/v0/destinations/rakuten/networkHandler.js b/src/v0/destinations/rakuten/networkHandler.js index 6c89d83947..4c97a23e51 100644 --- a/src/v0/destinations/rakuten/networkHandler.js +++ b/src/v0/destinations/rakuten/networkHandler.js @@ -18,7 +18,13 @@ const proxyRequest = async (request, destType) => { headers, method, }; - const response = await httpSend(requestOptions, { feature: 'proxy', destType }); + const response = await httpSend(requestOptions, { + feature: 'proxy', + destType, + endpointPath: '/ep', + requestMethod: 'GET', + module: 'dataDelivery', + }); return response; }; const extractContent = (xmlPayload, tagName) => { diff --git a/src/v0/destinations/salesforce/transform.js b/src/v0/destinations/salesforce/transform.js index 5ada9dfaa0..e791bffd46 100644 --- a/src/v0/destinations/salesforce/transform.js +++ b/src/v0/destinations/salesforce/transform.js @@ -120,6 +120,9 @@ async function getSaleforceIdForRecord( { destType: 'salesforce', feature: 'transformation', + endpointPath: '/parameterizedSearch', + requestMethod: 'GET', + module: 'router', }, ); if (!isHttpStatusSuccess(processedsfSearchResponse.status)) { @@ -233,6 +236,9 @@ async function getSalesforceIdFromPayload( { destType: 'salesforce', feature: 'transformation', + endpointPath: '/parameterizedSearch', + requestMethod: 'GET', + module: 'router', }, ); diff --git a/src/v0/destinations/salesforce/utils.js b/src/v0/destinations/salesforce/utils.js index 96735ecc17..85061ce2b2 100644 --- a/src/v0/destinations/salesforce/utils.js +++ b/src/v0/destinations/salesforce/utils.js @@ -133,6 +133,9 @@ const getAccessToken = async (destination) => { { destType: 'salesforce', feature: 'transformation', + endpointPath: '/services/oauth2/token', + requestMethod: 'POST', + module: 'router', }, ); // If the request fails, throwing error. diff --git a/src/v0/destinations/sendgrid/deleteUsers.js b/src/v0/destinations/sendgrid/deleteUsers.js index 8410f41296..ccd277d90d 100644 --- a/src/v0/destinations/sendgrid/deleteUsers.js +++ b/src/v0/destinations/sendgrid/deleteUsers.js @@ -85,6 +85,9 @@ const userDeletionHandler = async (userAttributes, config) => { const deletionResponse = await httpDELETE(endpoint, requestOptions, { destType: 'sendgrid', feature: 'deleteUsers', + endpointPath: '/marketing/contacts', + requestMethod: 'DELETE', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -94,6 +97,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/sendgrid/util.js b/src/v0/destinations/sendgrid/util.js index 1df34bfe69..7105c5cda5 100644 --- a/src/v0/destinations/sendgrid/util.js +++ b/src/v0/destinations/sendgrid/util.js @@ -445,6 +445,9 @@ const fetchCustomFields = async (destination) => { const resonse = await httpGET(endpoint, requestOptions, { destType: 'sendgrid', feature: 'transformation', + endpointPath: '/marketing/field_definitions', + requestMethod: 'GET', + module: 'router', }); const processedResponse = processAxiosResponse(resonse); if (isHttpStatusSuccess(processedResponse.status)) { diff --git a/src/v0/destinations/sendinblue/util.js b/src/v0/destinations/sendinblue/util.js index 9ad37fc9b7..9fded8e493 100644 --- a/src/v0/destinations/sendinblue/util.js +++ b/src/v0/destinations/sendinblue/util.js @@ -60,6 +60,9 @@ const checkIfContactExists = async (identifier, apiKey) => { const contactDetailsResponse = await httpGET(endpoint, requestOptions, { destType: 'sendinblue', feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', }); const processedContactDetailsResponse = processAxiosResponse(contactDetailsResponse); diff --git a/src/v0/destinations/sfmc/transform.js b/src/v0/destinations/sfmc/transform.js index 7623d751f1..553ceb2828 100644 --- a/src/v0/destinations/sfmc/transform.js +++ b/src/v0/destinations/sfmc/transform.js @@ -44,7 +44,13 @@ const getToken = async (clientId, clientSecret, subdomain) => { { 'Content-Type': JSON_MIME_TYPE, }, - { destType: 'sfmc', feature: 'transformation' }, + { + destType: 'sfmc', + feature: 'transformation', + endpointPath: '/token', + requestMethod: 'POST', + module: 'router', + }, ); if (resp && resp.data) { return resp.data.access_token; diff --git a/src/v0/destinations/snapchat_custom_audience/networkHandler.js b/src/v0/destinations/snapchat_custom_audience/networkHandler.js index feedaea3e3..6044216293 100644 --- a/src/v0/destinations/snapchat_custom_audience/networkHandler.js +++ b/src/v0/destinations/snapchat_custom_audience/networkHandler.js @@ -43,6 +43,9 @@ const scAudienceProxyRequest = async (request) => { const response = await httpSend(requestOptions, { feature: 'proxy', destType: 'snapchat_custom_audience', + endpointPath: '/segments/segmentId/users', + requestMethod: requestOptions?.method, + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/sprig/deleteUsers.js b/src/v0/destinations/sprig/deleteUsers.js index a886bbbafc..01044adcd1 100644 --- a/src/v0/destinations/sprig/deleteUsers.js +++ b/src/v0/destinations/sprig/deleteUsers.js @@ -49,7 +49,9 @@ const userDeletionHandler = async (userAttributes, config) => { { destType: 'sprig', feature: 'deleteUsers', - endpointPath: 'api.sprig.com/v2/purge/visitors', + endpointPath: '/purge/visitors', + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -59,6 +61,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/the_trade_desk/networkHandler.js b/src/v0/destinations/the_trade_desk/networkHandler.js index 4d9e5321e7..aebbfc0785 100644 --- a/src/v0/destinations/the_trade_desk/networkHandler.js +++ b/src/v0/destinations/the_trade_desk/networkHandler.js @@ -37,7 +37,13 @@ const proxyRequest = async (request) => { headers: ProxyHeaders, method, }; - const response = await httpSend(requestOptions, { feature: 'proxy', destType: 'the_trade_desk' }); + const response = await httpSend(requestOptions, { + feature: 'proxy', + destType: 'the_trade_desk', + endpointPath: '/track/realtimeconversion', + requestMethod: 'POST', + module: 'dataDelivery', + }); return response; }; diff --git a/src/v0/destinations/trengo/transform.js b/src/v0/destinations/trengo/transform.js index 06e5496a1e..01c5cfeb25 100644 --- a/src/v0/destinations/trengo/transform.js +++ b/src/v0/destinations/trengo/transform.js @@ -90,7 +90,13 @@ const lookupContact = async (term, destination) => { Authorization: `Bearer ${destination.Config.apiToken}`, }, }, - { destType: 'trengo', feature: 'transformation' }, + { + destType: 'trengo', + feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', + }, ); } catch (err) { // check if exists err.response && err.response.status else 500 diff --git a/src/v0/destinations/user/utils.js b/src/v0/destinations/user/utils.js index 52fba2167e..f332d7a4a7 100644 --- a/src/v0/destinations/user/utils.js +++ b/src/v0/destinations/user/utils.js @@ -238,6 +238,8 @@ const createCompany = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/companies/`, + requestMethod: 'POST', + module: 'router', }); const data = processAxiosResponse(response); return data.response; @@ -279,6 +281,8 @@ const updateCompany = async (message, destination, company) => { destType: 'user', feature: 'transformation', endpointPath: `/companies/`, + requestMethod: 'PUT', + module: 'router', }); const data = processAxiosResponse(response); return data.response; @@ -306,6 +310,8 @@ const getUserByUserKey = async (apiKey, userKey, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); if (processedUserResponse.status === 200) { @@ -340,6 +346,8 @@ const getUserByEmail = async (apiKey, email, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search/?email`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -379,6 +387,8 @@ const getUserByPhoneNumber = async (apiKey, phoneNumber, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search/?phone_number`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -424,6 +434,8 @@ const getUserByCustomId = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/users-by-id/`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -460,6 +472,8 @@ const getCompanyByCustomId = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/companies-by-id/`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(response); if (processedUserResponse.status === 200) { diff --git a/src/v0/destinations/wootric/util.js b/src/v0/destinations/wootric/util.js index eb61a472cf..0ae0a4940b 100644 --- a/src/v0/destinations/wootric/util.js +++ b/src/v0/destinations/wootric/util.js @@ -47,6 +47,8 @@ const getAccessToken = async (destination) => { destType: 'wootric', feature: 'transformation', endpointPath: `/oauth/token`, + requestMethod: 'POST', + module: 'router' }); const processedAuthResponse = processAxiosResponse(wootricAuthResponse); // If the request fails, throwing error. @@ -100,6 +102,8 @@ const retrieveUserDetails = async (endUserId, externalId, accessToken) => { destType: 'wootric', feature: 'transformation', endpointPath: `/v1/end_users/`, + requestMethod: 'GET', + module: 'router' }); const processedUserResponse = processAxiosResponse(userResponse); diff --git a/src/v0/destinations/yahoo_dsp/util.js b/src/v0/destinations/yahoo_dsp/util.js index d41716935f..255f84d1c9 100644 --- a/src/v0/destinations/yahoo_dsp/util.js +++ b/src/v0/destinations/yahoo_dsp/util.js @@ -137,6 +137,9 @@ const getAccessToken = async (destination) => { const dspAuthorisationData = await httpSend(request, { destType: 'yahoo_dsp', feature: 'transformation', + endpointPath: '/identity/oauth2/access_token', + requestMethod: 'POST', + module: 'router', }); // If the request fails, throwing error. if (dspAuthorisationData.success === false) { diff --git a/src/v0/destinations/zendesk/transform.js b/src/v0/destinations/zendesk/transform.js index bf2bc01ed2..5862014784 100644 --- a/src/v0/destinations/zendesk/transform.js +++ b/src/v0/destinations/zendesk/transform.js @@ -36,7 +36,7 @@ const tags = require('../../util/tags'); const { JSON_MIME_TYPE } = require('../../util/constant'); const CONTEXT_TRAITS_KEY_PATH = 'context.traits'; - +const endpointPath = '/users/search.json'; function responseBuilder(message, headers, payload, endpoint) { const response = defaultRequestConfig(); @@ -102,6 +102,9 @@ const payloadBuilderforUpdatingEmail = async (userId, headers, userEmail, baseEn const res = await httpGET(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: 'users/userId/identities', + requestMethod: 'POST', + module: 'router', }); if (res?.response?.data?.count > 0) { const { identities } = res.response.data; @@ -147,6 +150,9 @@ async function createUserFields(url, config, newFields, fieldJson) { const response = await myAxios.post(url, fieldData, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/identities', + requestMethod: 'POST', + module: 'router', }); if (response.status !== 201) { logger.debug(`${NAME}:: Failed to create User Field : `, field); @@ -176,6 +182,8 @@ async function checkAndCreateUserFields( const response = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + requestMethod: 'POST', + module: 'router', }); const fields = get(response.data, fieldJson); if (response.data && fields) { @@ -253,6 +261,9 @@ const getUserIdByExternalId = async (message, headers, baseEndpoint) => { const resp = await httpGET(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (resp?.response?.data?.count > 0) { @@ -283,6 +294,9 @@ async function getUserId(message, headers, baseEndpoint, type) { const resp = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (!resp || !resp.data || resp.data.count === 0) { logger.debug(`${NAME}:: User not found`); @@ -307,6 +321,9 @@ async function isUserAlreadyAssociated(userId, orgId, headers, baseEndpoint) { const response = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/organization_memberships.json', + requestMethod: 'GET', + module: 'router', }); if (response?.data?.organization_memberships?.[0]?.organization_id === orgId) { return true; @@ -339,6 +356,9 @@ async function createUser(message, headers, destinationConfig, baseEndpoint, typ const resp = await myAxios.post(url, payload, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/create_or_update.json', + requestMethod: 'POST', + module: 'router', }); if (!resp.data || !resp.data.user || !resp.data.user.id) { @@ -420,6 +440,9 @@ async function createOrganization(message, category, headers, destinationConfig, const resp = await myAxios.post(url, payload, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/organizations/create_or_update.json', + requestMethod: 'POST', + module: 'router', }); if (!resp.data || !resp.data.organization) { @@ -488,6 +511,9 @@ async function processIdentify(message, destinationConfig, headers, baseEndpoint const response = await myAxios.get(membershipUrl, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/organization_memberships.json', + requestMethod: 'GET', + module: 'router', }); if ( response.data && @@ -535,6 +561,9 @@ async function processTrack(message, destinationConfig, headers, baseEndpoint) { const userResponse = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (!get(userResponse, 'data.users.0.id') || userResponse.data.count === 0) { const { zendeskUserId, email } = await createUser( diff --git a/src/v0/util/tags.js b/src/v0/util/tags.js index 18f00f963f..dce8c0a338 100644 --- a/src/v0/util/tags.js +++ b/src/v0/util/tags.js @@ -13,6 +13,7 @@ const TAG_NAMES = { DESTINATION_ID: 'destinationId', WORKSPACE_ID: 'workspaceId', SOURCE_ID: 'sourceId', + STATUS: 'statusCode', }; const MODULES = { @@ -51,7 +52,7 @@ const ERROR_TYPES = { OAUTH_SECRET: 'oAuthSecret', UNSUPPORTED: 'unsupported', REDIS: 'redis', - FILTERED: 'filtered' + FILTERED: 'filtered', }; const METADATA = { From 69e43b6ffadeaec87b7440da34a341890ceba252 Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:04:54 +0530 Subject: [PATCH 29/33] fix: clevertap remove stringification of array object properties (#3048) * fix: remove ztringification for nested objects and arrays in profileData * fix: remove ztringification for nested objects and arrays in event data for custom events * chore: update test on category-unsubscribe * fix: revert "remove ztringification for nested objects and arrays in event data for custom events" commit This reverts commit 9157b28dcf5c54dec85a553d0d3f8fec5bdb5b35. * fix: allow stringification for subscription properties only --------- Co-authored-by: Sudip Paul <67197965+ItsSudip@users.noreply.github.com> --- src/v0/destinations/clevertap/transform.js | 9 +++++++-- .../destinations/clevertap/processor/data.ts | 16 +++++++++------- .../destinations/clevertap/router/data.ts | 4 ++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/v0/destinations/clevertap/transform.js b/src/v0/destinations/clevertap/transform.js index efcd101668..b369f507f8 100644 --- a/src/v0/destinations/clevertap/transform.js +++ b/src/v0/destinations/clevertap/transform.js @@ -83,16 +83,21 @@ const responseWrapper = (payload, destination) => { } * } - * This function stringify the payload attributes if it's an array or objects. + * This function stringify the payload attributes if it's an array or objects. The keys that are not stringified are present in the `stringifyExcludeList` array. * @param {*} payload * @returns * return the final payload after converting to the relevant data-types. */ const convertObjectAndArrayToString = (payload, event) => { const finalPayload = {}; + const stringifyExcludeList = ['category-unsubscribe', 'category-resubscribe']; if (payload) { Object.keys(payload).forEach((key) => { - if (payload[key] && (Array.isArray(payload[key]) || typeof payload[key] === 'object')) { + if ( + payload[key] && + (Array.isArray(payload[key]) || typeof payload[key] === 'object') && + !stringifyExcludeList.includes(key) + ) { finalPayload[key] = JSON.stringify(payload[key]); } else { finalPayload[key] = payload[key]; diff --git a/test/integrations/destinations/clevertap/processor/data.ts b/test/integrations/destinations/clevertap/processor/data.ts index d79ebaa8da..6309c5ec8a 100644 --- a/test/integrations/destinations/clevertap/processor/data.ts +++ b/test/integrations/destinations/clevertap/processor/data.ts @@ -52,6 +52,7 @@ export const data = [ state: 'WB', street: '', }, + 'category-unsubscribe': { email: ['Marketing', 'Transactional'] }, }, integrations: { All: true, @@ -98,10 +99,11 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', + 'category-unsubscribe': { email: ['Marketing', 'Transactional'] }, }, identity: 'anon_id', }, @@ -242,8 +244,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -968,8 +970,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -1111,8 +1113,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -1692,8 +1694,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -2077,8 +2079,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', first_name: 'John', @@ -2230,8 +2232,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', first_name: 'John', diff --git a/test/integrations/destinations/clevertap/router/data.ts b/test/integrations/destinations/clevertap/router/data.ts index 4f1723a7da..5f25bbe83e 100644 --- a/test/integrations/destinations/clevertap/router/data.ts +++ b/test/integrations/destinations/clevertap/router/data.ts @@ -162,10 +162,10 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', - custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', + custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', }, objectId: 'anon_id', }, From 41f4078011ef54334bb9ecc11a7b2ccc8831a4aa Mon Sep 17 00:00:00 2001 From: Dilip Kola <33080863+koladilip@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:14:44 +0530 Subject: [PATCH 30/33] fix: reddit revenue mapping for floating point values (#3118) * fix: reddit revenue mapping for floating point values * fix: reddit revenue mapping for floating point values --- .../v2/destinations/reddit/procWorkflow.yaml | 6 +- .../destinations/reddit/processor/data.ts | 167 ++++++++++++++++++ 2 files changed, 171 insertions(+), 2 deletions(-) diff --git a/src/cdk/v2/destinations/reddit/procWorkflow.yaml b/src/cdk/v2/destinations/reddit/procWorkflow.yaml index e6c05ef86d..1cf195707d 100644 --- a/src/cdk/v2/destinations/reddit/procWorkflow.yaml +++ b/src/cdk/v2/destinations/reddit/procWorkflow.yaml @@ -57,12 +57,14 @@ steps: - name: customFields condition: $.outputs.prepareTrackPayload.eventType.tracking_type === "Purchase" + reference: "https://ads-api.reddit.com/docs/v2/#tag/Conversions/paths/~1api~1v2.0~1conversions~1events~1%7Baccount_id%7D/post" template: | + const revenue_in_cents = .message.properties.revenue ? Math.round(Number(.message.properties.revenue)*100) const customFields = .message.().({ "currency": .properties.currency, - "value_decimal": .properties.revenue !== undefined ? Number(.properties.revenue) : undefined, + "value_decimal": revenue_in_cents ? revenue_in_cents / 100, "item_count": (Array.isArray(.properties.products) && .properties.products.length) || (.properties.itemCount && Number(.properties.itemCount)), - "value": .properties.revenue !== undefined ? Number(.properties.revenue)*100 : undefined, + "value": revenue_in_cents, "conversion_id": .properties.conversionId || .messageId, }); $.removeUndefinedAndNullValues(customFields) diff --git a/test/integrations/destinations/reddit/processor/data.ts b/test/integrations/destinations/reddit/processor/data.ts index f3cd4ebf7b..49e0cd2baa 100644 --- a/test/integrations/destinations/reddit/processor/data.ts +++ b/test/integrations/destinations/reddit/processor/data.ts @@ -166,6 +166,173 @@ export const data = [ }, }, }, + { + name: 'reddit', + description: 'Track call with order completed event with floating point values for revenue', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + context: { + traits: { + email: 'testone@gmail.com', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + ip: '54.100.200.255', + device: { + advertisingId: 'asfds7fdsihf734b34j43f', + }, + os: { + name: 'android', + }, + }, + type: 'track', + session_id: '16733896350494', + originalTimestamp: '2019-10-14T09:03:17.562Z', + anonymousId: '123456', + event: 'Order Completed', + userId: 'testuserId1', + properties: { + checkout_id: '12345', + order_id: '1234', + affiliation: 'Apple Store', + total: 20, + revenue: 14.985, + shipping: 4, + tax: 1, + discount: 1.5, + coupon: 'ImagePro', + currency: 'USD', + products: [ + { + product_id: '123', + sku: 'G-32', + name: 'Monopoly', + price: 14, + quantity: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '345', + sku: 'F-32', + name: 'UNO', + price: 3.45, + quantity: 2, + category: 'Games', + }, + ], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + destination: { + Config: { + accountId: 'a2_fsddXXXfsfd', + hashData: true, + eventsMapping: [ + { + from: 'Order Completed', + to: 'Purchase', + }, + ], + }, + DestinationDefinition: { Config: { cdkV2Enabled: true } }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + secret: { + accessToken: 'dummyAccessToken', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', + headers: { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'Purchase', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: + '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: + 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 2, + currency: 'USD', + value: 1499, + value_decimal: 14.99, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + secret: { + accessToken: 'dummyAccessToken', + }, + }, + statusCode: 200, + }, + ], + }, + }, + }, { name: 'reddit', description: 'Track call with product list viewed event', From 8351b5cbbf81bbc14b2f884feaae4ad3ca59a39a Mon Sep 17 00:00:00 2001 From: Sankeerth Date: Mon, 26 Feb 2024 16:59:35 +0530 Subject: [PATCH 31/33] fix: metadata structure correction (#3119) --- package-lock.json | 283 +-------- package.json | 2 +- src/controllers/destination.ts | 15 + src/legacy/router.js | 3 +- src/v0/destinations/bqstream/transform.js | 5 - .../campaign_manager/transform.js | 6 - src/v0/destinations/clevertap/transform.js | 8 - src/v0/destinations/customerio/transform.js | 5 - .../transform.js | 6 - .../google_cloud_function/transform.js | 11 +- src/v0/destinations/googlesheets/transform.js | 5 - src/v0/destinations/hs/transform.js | 10 +- src/v0/destinations/iterable/transform.js | 6 - src/v0/destinations/kafka/transform.js | 5 - src/v0/destinations/klaviyo/transform.js | 5 - src/v0/destinations/lambda/transform.js | 3 +- src/v0/destinations/mailchimp/transform.js | 5 - src/v0/destinations/mailjet/transform.js | 5 - src/v0/destinations/mailmodo/transform.js | 6 - src/v0/destinations/marketo/transform.js | 7 +- .../marketo_static_list/transform.js | 12 +- .../marketo_static_list/transformV2.js | 7 +- src/v0/destinations/mp/transform.js | 6 - src/v0/destinations/ometria/transform.js | 5 - src/v0/destinations/pardot/transform.js | 6 - .../destinations/pinterest_tag/transform.js | 6 - src/v0/destinations/salesforce/transform.js | 7 +- src/v0/destinations/sendgrid/transform.js | 5 - .../snapchat_conversion/transform.js | 6 - src/v0/destinations/tiktok_ads/transform.js | 5 - src/v0/destinations/tiktok_ads/transformV2.js | 5 - .../tiktok_ads_offline_events/transform.js | 6 - src/v0/util/index.js | 23 +- .../router/data.ts | 554 +++++++++++------- 34 files changed, 392 insertions(+), 662 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d4f32bd5a..153851dab2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/integrations-lib": "^0.2.2", + "@rudderstack/integrations-lib": "^0.2.4", "@rudderstack/workflow-engine": "^0.7.2", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", @@ -154,7 +154,6 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3235,7 +3234,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -3250,7 +3248,6 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -3259,7 +3256,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -3282,7 +3278,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3297,14 +3292,12 @@ "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/@eslint/js": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3334,7 +3327,6 @@ "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", @@ -3348,7 +3340,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "engines": { "node": ">=12.22" }, @@ -3360,8 +3351,7 @@ "node_modules/@humanwhocodes/object-schema": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==" }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", @@ -4348,7 +4338,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -4361,7 +4350,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -4370,7 +4358,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4468,14 +4455,15 @@ } }, "node_modules/@rudderstack/integrations-lib": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@rudderstack/integrations-lib/-/integrations-lib-0.2.2.tgz", - "integrity": "sha512-LilQsYcYh/4XXHNmYHM164fCbO5U3uvlw7k1wiCvFOR0MS1RhFXD9sPgCYpri683Jy3gqq1FrKN1EFj7oWAMjw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@rudderstack/integrations-lib/-/integrations-lib-0.2.4.tgz", + "integrity": "sha512-32Zose9aOPNWd4EyUNuS5YY+Vq4LYMuDcabJ+s3t1ZfHHMfISlDNF02b60MWgOrU8PARYC+siDs5wgA6xfZpzQ==", "dependencies": { - "@rudderstack/workflow-engine": "^0.5.7", "axios": "^1.4.0", "axios-mock-adapter": "^1.22.0", "crypto": "^1.0.1", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", "get-value": "^3.0.1", "handlebars": "^4.7.8", "lodash": "^4.17.21", @@ -4487,54 +4475,6 @@ "winston": "^3.11.0" } }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-4.0.0.tgz", - "integrity": "sha512-MHGJyjE7TX9aaqXj7zk2ppnFUOhaDs5sP+HtNS0evOxn72c+5njUmyJmpGd7TfyoDznZlHMmdo/xGUdu2NIjNQ==", - "dependencies": { - "@aws-crypto/util": "^4.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-4.0.0.tgz", - "integrity": "sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ==", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/json-template-engine": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.5.5.tgz", - "integrity": "sha512-p3HdTqgZiJjjZmjaHN2paa1e87ifGE5UjkA4zdvge4bBzJbKKMQNWqRg6I96SwoA+hsxNkW/f9R83SPLU9t7LA==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/workflow-engine": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.5.8.tgz", - "integrity": "sha512-H1aCowYqTnOoqJtL9cGDhdhoGNl+KzqmVbSjFmE7n75onZaIMs87+HQyW08jYxS9l1Uo4TL8SAvzFICqFqkBbw==", - "dependencies": { - "@aws-crypto/sha256-js": "^4.0.0", - "@rudderstack/json-template-engine": "^0.5.5", - "js-yaml": "^4.1.0", - "jsonata": "^2.0.3", - "lodash": "^4.17.21", - "object-sizeof": "^2.6.3" - } - }, "node_modules/@rudderstack/json-template-engine": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.5.tgz", @@ -5466,14 +5406,12 @@ "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/keygrip": { "version": "1.0.6", @@ -5555,8 +5493,7 @@ "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==" }, "node_modules/@types/send": { "version": "0.17.4", @@ -5607,7 +5544,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -5641,7 +5577,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5668,7 +5603,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" @@ -5685,7 +5619,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, "dependencies": { "@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/utils": "5.62.0", @@ -5712,7 +5645,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5725,7 +5657,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0", @@ -5752,7 +5683,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", @@ -5778,7 +5708,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" @@ -5794,8 +5723,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/abbrev": { "version": "1.1.1", @@ -5818,7 +5746,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5830,7 +5757,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -6030,7 +5956,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -6058,7 +5983,6 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6077,7 +6001,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, "engines": { "node": ">=8" } @@ -6086,7 +6009,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6105,7 +6027,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6123,7 +6044,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6141,7 +6061,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", @@ -6205,7 +6124,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -6620,7 +6538,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -6827,7 +6744,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -7559,8 +7475,7 @@ "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -8568,7 +8483,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -8741,8 +8655,7 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -8789,7 +8702,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -9162,7 +9074,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "dependencies": { "path-type": "^4.0.0" }, @@ -9174,7 +9085,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -9404,7 +9314,6 @@ "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "arraybuffer.prototype.slice": "^1.0.2", @@ -9457,7 +9366,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.2", "has-tostringtag": "^1.0.0", @@ -9471,7 +9379,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, "dependencies": { "hasown": "^2.0.0" } @@ -9480,7 +9387,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -9564,7 +9470,6 @@ "version": "8.56.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9619,7 +9524,6 @@ "version": "15.0.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", @@ -9638,7 +9542,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -9647,7 +9550,6 @@ "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", - "dev": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0" }, @@ -9674,7 +9576,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -9685,7 +9586,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9694,7 +9594,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, "dependencies": { "debug": "^3.2.7" }, @@ -9711,7 +9610,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9720,7 +9618,6 @@ "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "dev": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -9751,7 +9648,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9760,7 +9656,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -9772,7 +9667,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -9941,7 +9835,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -9954,7 +9847,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -9966,7 +9858,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -9982,7 +9873,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -9997,7 +9887,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10013,7 +9902,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -10024,14 +9912,12 @@ "node_modules/eslint/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -10043,7 +9929,6 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -10059,7 +9944,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10068,7 +9952,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -10076,14 +9959,12 @@ "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -10095,7 +9976,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -10125,7 +10005,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -10137,7 +10016,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10146,7 +10024,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -10158,7 +10035,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10167,7 +10043,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10176,7 +10051,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10287,7 +10161,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -10303,7 +10176,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -10314,14 +10186,12 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fast-printf": { "version": "1.6.9", @@ -10366,7 +10236,6 @@ "version": "1.16.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -10404,7 +10273,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -10467,7 +10335,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -10495,7 +10362,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -10542,7 +10408,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -10555,8 +10420,7 @@ "node_modules/flatted": { "version": "3.2.9", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "node_modules/flatten": { "version": "1.0.3", @@ -10593,7 +10457,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -10745,7 +10608,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -10763,7 +10625,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11086,7 +10947,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -11342,7 +11202,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -11432,7 +11291,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -11447,7 +11305,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -11462,7 +11319,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -11559,8 +11415,7 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/growly": { "version": "1.3.0", @@ -11609,7 +11464,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11947,7 +11801,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "dev": true, "engines": { "node": ">= 4" } @@ -11956,7 +11809,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -11972,7 +11824,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -12000,7 +11851,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -12162,7 +12012,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.2", "hasown": "^2.0.0", @@ -12228,7 +12077,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -12247,7 +12095,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -12259,7 +12106,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12312,7 +12158,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12335,7 +12180,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12365,7 +12209,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12421,7 +12264,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -12453,7 +12295,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12465,7 +12306,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -12474,7 +12314,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12507,7 +12346,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -12544,7 +12382,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12584,7 +12421,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -12607,7 +12443,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12622,7 +12457,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -12649,7 +12483,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -12700,7 +12533,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -12743,8 +12575,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isobject": { "version": "3.0.1", @@ -14553,8 +14384,7 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-diff": { "version": "1.0.6", @@ -14599,8 +14429,7 @@ "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -14694,7 +14523,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "dependencies": { "json-buffer": "3.0.1" } @@ -14944,7 +14772,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -15294,7 +15121,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -15371,8 +15197,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/lodash.mergewith": { "version": "4.6.2", @@ -16149,7 +15974,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -16166,7 +15990,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -16653,14 +16476,12 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/natural-compare-lite": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" }, "node_modules/negotiator": { "version": "0.6.3", @@ -16883,7 +16704,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -16923,7 +16743,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "define-properties": "^1.2.1", @@ -16941,7 +16760,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16955,7 +16773,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16972,7 +16789,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16984,7 +16800,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -17055,7 +16870,6 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -17215,7 +17029,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -17277,7 +17090,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -17359,7 +17171,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -17403,7 +17214,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -17418,7 +17228,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -17913,7 +17722,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "engines": { "node": ">= 0.8.0" } @@ -18136,7 +17944,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -18403,7 +18210,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -18604,7 +18410,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -18702,7 +18507,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -18734,7 +18538,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -18751,8 +18554,7 @@ "node_modules/safe-array-concat/node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/safe-buffer": { "version": "5.2.1", @@ -18786,7 +18588,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -18901,7 +18702,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "functions-have-names": "^1.2.3", @@ -18946,7 +18746,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -18958,7 +18757,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -19010,7 +18808,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, "engines": { "node": ">=8" } @@ -19696,7 +19493,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19713,7 +19509,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19727,7 +19522,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19816,7 +19610,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -20027,8 +19820,7 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, "node_modules/through": { "version": "2.3.8", @@ -20093,7 +19885,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -20251,7 +20042,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -20263,7 +20053,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, "dependencies": { "minimist": "^1.2.0" }, @@ -20275,7 +20064,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, "engines": { "node": ">=4" } @@ -20297,7 +20085,6 @@ "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, "dependencies": { "tslib": "^1.8.1" }, @@ -20311,14 +20098,12 @@ "node_modules/tsutils/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -20339,7 +20124,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -20363,7 +20147,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -20377,7 +20160,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -20395,7 +20177,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -20414,7 +20195,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -20434,7 +20214,6 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20481,7 +20260,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -20749,7 +20527,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -20764,7 +20541,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -20786,7 +20562,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.4", diff --git a/package.json b/package.json index a1053c0496..455819a3b7 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/integrations-lib": "^0.2.2", + "@rudderstack/integrations-lib": "^0.2.4", "@rudderstack/workflow-engine": "^0.7.2", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", diff --git a/src/controllers/destination.ts b/src/controllers/destination.ts index 71075d1b4c..d8b3c94524 100644 --- a/src/controllers/destination.ts +++ b/src/controllers/destination.ts @@ -15,6 +15,7 @@ import logger from '../logger'; import { getIntegrationVersion } from '../util/utils'; import tags from '../v0/util/tags'; import { DynamicConfigParser } from '../util/dynamicConfigParser'; +import { checkInvalidRtTfEvents } from '../v0/util'; export class DestinationController { public static async destinationTransformAtProcessor(ctx: Context) { @@ -101,6 +102,20 @@ export class DestinationController { const routerRequest = ctx.request.body as RouterTransformationRequest; const destination = routerRequest.destType; let events = routerRequest.input; + const errorRespEvents = checkInvalidRtTfEvents(events); + if (errorRespEvents.length > 0) { + errorRespEvents[0].metadata = [ + { + destType: destination, + }, + ]; + logger.debug( + `[${destination}] Invalid router transform payload structure: ${JSON.stringify(events)}`, + ); + ctx.body = { output: errorRespEvents }; + ControllerUtility.postProcess(ctx); + return ctx; + } const metaTags = MiscService.getMetaTags(events[0].metadata); stats.histogram('dest_transform_input_events', events.length, { destination, diff --git a/src/legacy/router.js b/src/legacy/router.js index f8deb3fe62..9dd83b5988 100644 --- a/src/legacy/router.js +++ b/src/legacy/router.js @@ -7,7 +7,7 @@ const Router = require('@koa/router'); const lodash = require('lodash'); const fs = require('fs'); const path = require('path'); -const { PlatformError } = require('@rudderstack/integrations-lib'); +const { PlatformError, getErrorRespEvents } = require('@rudderstack/integrations-lib'); const logger = require('../logger'); const stats = require('../util/stats'); const { SUPPORTED_VERSIONS, API_VERSION } = require('../routes/utils/constants'); @@ -18,7 +18,6 @@ const { isNonFuncObject, getMetadata, generateErrorObject, - getErrorRespEvents, isCdkDestination, checkAndCorrectUserId, } = require('../v0/util'); diff --git a/src/v0/destinations/bqstream/transform.js b/src/v0/destinations/bqstream/transform.js index 598a97946d..8ee96aecf1 100644 --- a/src/v0/destinations/bqstream/transform.js +++ b/src/v0/destinations/bqstream/transform.js @@ -5,7 +5,6 @@ const { EventType } = require('../../../constants'); const { defaultBatchRequestConfig, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, groupEventsByType, } = require('../../util'); @@ -130,10 +129,6 @@ const processEachTypedEventList = ( }; const processRouterDest = (inputs) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs, DESTINATION); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const finalResp = []; const batchedEvents = groupEventsByType(inputs); diff --git a/src/v0/destinations/campaign_manager/transform.js b/src/v0/destinations/campaign_manager/transform.js index 3b480dbac2..14bc6d2c19 100644 --- a/src/v0/destinations/campaign_manager/transform.js +++ b/src/v0/destinations/campaign_manager/transform.js @@ -9,7 +9,6 @@ const { removeUndefinedAndNullValues, getSuccessRespEvents, isDefinedAndNotNull, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getAccessToken, } = require('../../util'); @@ -245,11 +244,6 @@ const batchEvents = (eventChunksArray) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchErrorRespList = []; const eventChunksArray = []; const { destination } = inputs[0]; diff --git a/src/v0/destinations/clevertap/transform.js b/src/v0/destinations/clevertap/transform.js index b369f507f8..51ed5c851e 100644 --- a/src/v0/destinations/clevertap/transform.js +++ b/src/v0/destinations/clevertap/transform.js @@ -22,7 +22,6 @@ const { handleRtTfSingleEventError, batchMultiplexedEvents, getSuccessRespEvents, - checkInvalidRtTfEvents, } = require('../../util'); const { generateClevertapBatchedPayload } = require('./utils'); @@ -389,13 +388,6 @@ const processEvent = (message, destination) => { const process = (event) => processEvent(event.message, event.destination); const processRouterDest = (inputs, reqMetadata) => { - // const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); - // return respList; - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const eventsChunk = []; const errorRespList = []; // const { destination } = inputs[0]; diff --git a/src/v0/destinations/customerio/transform.js b/src/v0/destinations/customerio/transform.js index be4486717c..6f2e053001 100644 --- a/src/v0/destinations/customerio/transform.js +++ b/src/v0/destinations/customerio/transform.js @@ -5,7 +5,6 @@ const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { EventType, MappedToDestinationKey } = require('../../../constants'); const { - getErrorRespEvents, getSuccessRespEvents, defaultRequestConfig, addExternalIdToTraits, @@ -174,10 +173,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 46cde72771..68d4d01fa7 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -9,7 +9,6 @@ const { handleRtTfSingleEventError, defaultBatchRequestConfig, getSuccessRespEvents, - checkInvalidRtTfEvents, combineBatchRequestsWithSameJobIds, } = require('../../util'); const { @@ -186,11 +185,6 @@ const batchEvents = (storeSalesEvents) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const storeSalesEvents = []; // list containing store sales events in batched format const clickCallEvents = []; // list containing click and call events in batched format const errorRespList = []; diff --git a/src/v0/destinations/google_cloud_function/transform.js b/src/v0/destinations/google_cloud_function/transform.js index b218615b44..5e870b9581 100644 --- a/src/v0/destinations/google_cloud_function/transform.js +++ b/src/v0/destinations/google_cloud_function/transform.js @@ -1,9 +1,5 @@ const lodash = require('lodash'); -const { - getSuccessRespEvents, - checkInvalidRtTfEvents, - handleRtTfSingleEventError, -} = require('../../util'); +const { getSuccessRespEvents, handleRtTfSingleEventError } = require('../../util'); const { generateBatchedPayload, validateDestinationConfig } = require('./util'); @@ -40,11 +36,6 @@ function batchEvents(successRespList, maxBatchSize = 10) { // Router transform with batching by default const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const successResponseList = []; const errorRespList = []; const { destination } = inputs[0]; diff --git a/src/v0/destinations/googlesheets/transform.js b/src/v0/destinations/googlesheets/transform.js index 6e27f6192c..79dcf1bdf2 100644 --- a/src/v0/destinations/googlesheets/transform.js +++ b/src/v0/destinations/googlesheets/transform.js @@ -5,7 +5,6 @@ const { getValueFromMessage, getSuccessRespEvents, handleRtTfSingleEventError, - checkInvalidRtTfEvents, } = require('../../util'); const SOURCE_KEYS = ['properties', 'traits', 'context.traits']; @@ -111,10 +110,6 @@ const process = (event) => { const processRouterDest = async (inputs, reqMetadata) => { const successRespList = []; const errorRespList = []; - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } await Promise.all( inputs.map(async (input) => { try { diff --git a/src/v0/destinations/hs/transform.js b/src/v0/destinations/hs/transform.js index c26e024a6c..9eed244af4 100644 --- a/src/v0/destinations/hs/transform.js +++ b/src/v0/destinations/hs/transform.js @@ -1,11 +1,7 @@ const get = require('get-value'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { EventType } = require('../../../constants'); -const { - checkInvalidRtTfEvents, - handleRtTfSingleEventError, - getDestinationExternalIDInfoForRetl, -} = require('../../util'); +const { handleRtTfSingleEventError, getDestinationExternalIDInfoForRetl } = require('../../util'); const { API_VERSION } = require('./config'); const { processLegacyIdentify, @@ -71,10 +67,6 @@ const process = async (event) => { // we are batching by default at routerTransform const processRouterDest = async (inputs, reqMetadata) => { let tempInputs = inputs; - const errorRespEvents = checkInvalidRtTfEvents(tempInputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const successRespList = []; const errorRespList = []; diff --git a/src/v0/destinations/iterable/transform.js b/src/v0/destinations/iterable/transform.js index 64bdcfcfa4..207a8d1186 100644 --- a/src/v0/destinations/iterable/transform.js +++ b/src/v0/destinations/iterable/transform.js @@ -18,7 +18,6 @@ const { const { constructPayload, defaultRequestConfig, - checkInvalidRtTfEvents, defaultPostRequestConfig, handleRtTfSingleEventError, removeUndefinedAndNullValues, @@ -162,11 +161,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchedEvents = batchEvents(inputs); const response = await Promise.all( batchedEvents.map(async (listOfEvents) => { diff --git a/src/v0/destinations/kafka/transform.js b/src/v0/destinations/kafka/transform.js index b08c717475..78f278575a 100644 --- a/src/v0/destinations/kafka/transform.js +++ b/src/v0/destinations/kafka/transform.js @@ -6,7 +6,6 @@ const { getHashFromArray, removeUndefinedAndNullValues, getSuccessRespEvents, - getErrorRespEvents, } = require('../../util'); const filterConfigTopics = (message, destination) => { @@ -38,10 +37,6 @@ const filterConfigTopics = (message, destination) => { const batch = (destEvents) => { const respList = []; - if (!Array.isArray(destEvents) || destEvents.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } // Grouping the events by topic const groupedEvents = groupBy(destEvents, (event) => event.message.topic); diff --git a/src/v0/destinations/klaviyo/transform.js b/src/v0/destinations/klaviyo/transform.js index 3c2f8137f2..a0fe3e81a7 100644 --- a/src/v0/destinations/klaviyo/transform.js +++ b/src/v0/destinations/klaviyo/transform.js @@ -32,7 +32,6 @@ const { addExternalIdToTraits, adduserIdFromExternalId, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, flattenJson, isNewStatusCodesAccepted, @@ -320,10 +319,6 @@ const getEventChunks = (event, subscribeRespList, nonSubscribeRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let batchResponseList = []; const batchErrorRespList = []; const subscribeRespList = []; diff --git a/src/v0/destinations/lambda/transform.js b/src/v0/destinations/lambda/transform.js index 1570a69ec3..efc68b89d6 100644 --- a/src/v0/destinations/lambda/transform.js +++ b/src/v0/destinations/lambda/transform.js @@ -1,5 +1,6 @@ const _ = require('lodash'); -const { getErrorRespEvents, getSuccessRespEvents } = require('../../util'); +const { getErrorRespEvents } = require('@rudderstack/integrations-lib'); +const { getSuccessRespEvents } = require('../../util'); const { ConfigurationError } = require('@rudderstack/integrations-lib'); const DEFAULT_INVOCATION_TYPE = 'Event'; // asynchronous invocation diff --git a/src/v0/destinations/mailchimp/transform.js b/src/v0/destinations/mailchimp/transform.js index 894f70672a..87f547c124 100644 --- a/src/v0/destinations/mailchimp/transform.js +++ b/src/v0/destinations/mailchimp/transform.js @@ -3,7 +3,6 @@ const { InstrumentationError, ConfigurationError } = require('@rudderstack/integ const { defaultPutRequestConfig, handleRtTfSingleEventError, - checkInvalidRtTfEvents, constructPayload, defaultPostRequestConfig, isDefinedAndNotNull, @@ -162,10 +161,6 @@ const getEventChunks = (event, identifyRespList, trackRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let batchResponseList = []; const batchErrorRespList = []; const identifyRespList = []; diff --git a/src/v0/destinations/mailjet/transform.js b/src/v0/destinations/mailjet/transform.js index 9156bf45e9..78b4f766d1 100644 --- a/src/v0/destinations/mailjet/transform.js +++ b/src/v0/destinations/mailjet/transform.js @@ -1,7 +1,6 @@ const lodash = require('lodash'); const { TransformationError, InstrumentationError } = require('@rudderstack/integrations-lib'); const { - getErrorRespEvents, getSuccessRespEvents, defaultRequestConfig, defaultPostRequestConfig, @@ -121,10 +120,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/mailmodo/transform.js b/src/v0/destinations/mailmodo/transform.js index 756522939d..a61ca8a73d 100644 --- a/src/v0/destinations/mailmodo/transform.js +++ b/src/v0/destinations/mailmodo/transform.js @@ -10,7 +10,6 @@ const { defaultPostRequestConfig, defaultBatchRequestConfig, removeUndefinedAndNullValues, - getErrorRespEvents, getSuccessRespEvents, handleRtTfSingleEventError, } = require('../../util'); @@ -191,11 +190,6 @@ function getEventChunks(event, identifyEventChunks, eventResponseList) { } const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } - const identifyEventChunks = []; // list containing identify events in batched format const eventResponseList = []; // list containing other events in batched format const errorRespList = []; diff --git a/src/v0/destinations/marketo/transform.js b/src/v0/destinations/marketo/transform.js index 5000ef506b..b811596f95 100644 --- a/src/v0/destinations/marketo/transform.js +++ b/src/v0/destinations/marketo/transform.js @@ -7,6 +7,7 @@ const { InstrumentationError, ConfigurationError, UnauthorizedError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const stats = require('../../../util/stats'); const { EventType, MappedToDestinationKey } = require('../../../constants'); @@ -28,10 +29,8 @@ const { getFieldValueFromMessage, getDestinationExternalID, getSuccessRespEvents, - getErrorRespEvents, isDefinedAndNotNull, generateErrorObject, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util'); const Cache = require('../../util/cache'); @@ -456,10 +455,6 @@ const process = async (event) => { const processRouterDest = async (inputs, reqMetadata) => { // Token needs to be generated for marketo which will be done on input level. // If destination information is not present Error should be thrown - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let token; try { token = await getAuthToken(formatConfig(inputs[0].destination)); diff --git a/src/v0/destinations/marketo_static_list/transform.js b/src/v0/destinations/marketo_static_list/transform.js index 294e34f91b..92c137c614 100644 --- a/src/v0/destinations/marketo_static_list/transform.js +++ b/src/v0/destinations/marketo_static_list/transform.js @@ -1,6 +1,10 @@ const lodash = require('lodash'); const cloneDeep = require('lodash/cloneDeep'); -const { InstrumentationError, UnauthorizedError } = require('@rudderstack/integrations-lib'); +const { + InstrumentationError, + UnauthorizedError, + getErrorRespEvents, +} = require('@rudderstack/integrations-lib'); const { defaultPostRequestConfig, defaultDeleteRequestConfig, @@ -9,11 +13,7 @@ const { } = require('../../util'); const { AUTH_CACHE_TTL, JSON_MIME_TYPE } = require('../../util/constant'); const { getIds, validateMessageType } = require('./util'); -const { - getDestinationExternalID, - defaultRequestConfig, - getErrorRespEvents, -} = require('../../util'); +const { getDestinationExternalID, defaultRequestConfig } = require('../../util'); const { formatConfig, MAX_LEAD_IDS_SIZE } = require('./config'); const Cache = require('../../util/cache'); const { getAuthToken } = require('../marketo/transform'); diff --git a/src/v0/destinations/marketo_static_list/transformV2.js b/src/v0/destinations/marketo_static_list/transformV2.js index 912d548d09..73d4bec8f8 100644 --- a/src/v0/destinations/marketo_static_list/transformV2.js +++ b/src/v0/destinations/marketo_static_list/transformV2.js @@ -1,5 +1,9 @@ const lodash = require('lodash'); -const { InstrumentationError, UnauthorizedError } = require('@rudderstack/integrations-lib'); +const { + InstrumentationError, + UnauthorizedError, + getErrorRespEvents, +} = require('@rudderstack/integrations-lib'); const { defaultPostRequestConfig, defaultDeleteRequestConfig, @@ -7,7 +11,6 @@ const { getSuccessRespEvents, isDefinedAndNotNull, generateErrorObject, - getErrorRespEvents, } = require('../../util'); const { JSON_MIME_TYPE } = require('../../util/constant'); const { MAX_LEAD_IDS_SIZE } = require('./config'); diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js index 24890c0eb1..493169cd4e 100644 --- a/src/v0/destinations/mp/transform.js +++ b/src/v0/destinations/mp/transform.js @@ -14,7 +14,6 @@ const { removeUndefinedValues, toUnixTimestampInMS, getFieldValueFromMessage, - checkInvalidRtTfEvents, handleRtTfSingleEventError, groupEventsByType, parseConfigArray, @@ -460,11 +459,6 @@ const process = (event) => processSingleMessage(event.message, event.destination // Ref: https://help.mixpanel.com/hc/en-us/articles/115004613766-Default-Properties-Collected-by-Mixpanel // Ref: https://help.mixpanel.com/hc/en-us/articles/115004561786-Track-UTM-Tags const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const groupedEvents = groupEventsByType(inputs); const response = await Promise.all( groupedEvents.map(async (listOfEvents) => { diff --git a/src/v0/destinations/ometria/transform.js b/src/v0/destinations/ometria/transform.js index 55038e10b8..5eff77bd15 100644 --- a/src/v0/destinations/ometria/transform.js +++ b/src/v0/destinations/ometria/transform.js @@ -14,7 +14,6 @@ const { getFieldValueFromMessage, getIntegrationsObj, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util/index'); const { @@ -250,10 +249,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const inputChunks = returnArrayOfSubarrays(inputs, MAX_BATCH_SIZE); const successList = []; const errorList = []; diff --git a/src/v0/destinations/pardot/transform.js b/src/v0/destinations/pardot/transform.js index b32b8967bd..3dbe57ecc7 100644 --- a/src/v0/destinations/pardot/transform.js +++ b/src/v0/destinations/pardot/transform.js @@ -44,7 +44,6 @@ const { getFieldValueFromMessage, removeUndefinedValues, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getAccessToken, } = require('../../util'); @@ -150,11 +149,6 @@ const processEvent = (metadata, message, destination) => { const process = (event) => processEvent(event.metadata, event.message, event.destination); const processRouterDest = (events, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(events); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const responseList = events.map((event) => { try { return getSuccessRespEvents(process(event), [event.metadata], event.destination); diff --git a/src/v0/destinations/pinterest_tag/transform.js b/src/v0/destinations/pinterest_tag/transform.js index ee7e2e5b19..f8ccfd48ea 100644 --- a/src/v0/destinations/pinterest_tag/transform.js +++ b/src/v0/destinations/pinterest_tag/transform.js @@ -5,7 +5,6 @@ const { defaultRequestConfig, defaultPostRequestConfig, getSuccessRespEvents, - getErrorRespEvents, constructPayload, defaultBatchRequestConfig, removeUndefinedAndNullValues, @@ -172,11 +171,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } - const successRespList = []; const batchErrorRespList = []; inputs.forEach((input) => { diff --git a/src/v0/destinations/salesforce/transform.js b/src/v0/destinations/salesforce/transform.js index e791bffd46..b8f032c5bf 100644 --- a/src/v0/destinations/salesforce/transform.js +++ b/src/v0/destinations/salesforce/transform.js @@ -3,6 +3,7 @@ const cloneDeep = require('lodash/cloneDeep'); const { InstrumentationError, NetworkInstrumentationError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const { EventType, MappedToDestinationKey } = require('../../../constants'); const { @@ -20,10 +21,8 @@ const { constructPayload, getFirstAndLastName, getSuccessRespEvents, - getErrorRespEvents, addExternalIdToTraits, getDestinationExternalIDObjectForRetl, - checkInvalidRtTfEvents, handleRtTfSingleEventError, generateErrorObject, isHttpStatusSuccess, @@ -354,10 +353,6 @@ async function process(event) { } const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let authInfo; try { authInfo = await collectAuthorizationInfo(inputs[0]); diff --git a/src/v0/destinations/sendgrid/transform.js b/src/v0/destinations/sendgrid/transform.js index 5038fedf7b..c32e34c489 100644 --- a/src/v0/destinations/sendgrid/transform.js +++ b/src/v0/destinations/sendgrid/transform.js @@ -9,7 +9,6 @@ const { ErrorMessage, isEmptyObject, constructPayload, - getErrorRespEvents, extractCustomFields, getValueFromMessage, defaultRequestConfig, @@ -236,10 +235,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/snapchat_conversion/transform.js b/src/v0/destinations/snapchat_conversion/transform.js index 37d321a468..6fec6313a4 100644 --- a/src/v0/destinations/snapchat_conversion/transform.js +++ b/src/v0/destinations/snapchat_conversion/transform.js @@ -12,7 +12,6 @@ const { getSuccessRespEvents, isAppleFamily, getValidDynamicFormConfig, - checkInvalidRtTfEvents, handleRtTfSingleEventError, batchMultiplexedEvents, } = require('../../util'); @@ -358,11 +357,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const eventsChunk = []; // temporary variable to divide payload into chunks const errorRespList = []; inputs.forEach((event) => { diff --git a/src/v0/destinations/tiktok_ads/transform.js b/src/v0/destinations/tiktok_ads/transform.js index bdf3a0defe..b8b10d4608 100644 --- a/src/v0/destinations/tiktok_ads/transform.js +++ b/src/v0/destinations/tiktok_ads/transform.js @@ -17,7 +17,6 @@ const { getDestinationExternalID, getFieldValueFromMessage, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, batchMultiplexedEvents, } = require('../../util'); @@ -248,10 +247,6 @@ const processRouterDest = async (inputs, reqMetadata) => { if (Config?.version === 'v2') { return processRouterDestV2(inputs, reqMetadata); } - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const trackResponseList = []; // list containing single track event in batched format const eventsChunk = []; // temporary variable to divide payload into chunks diff --git a/src/v0/destinations/tiktok_ads/transformV2.js b/src/v0/destinations/tiktok_ads/transformV2.js index 98f7d61e1e..48c5b19e64 100644 --- a/src/v0/destinations/tiktok_ads/transformV2.js +++ b/src/v0/destinations/tiktok_ads/transformV2.js @@ -13,7 +13,6 @@ const { isDefinedAndNotNullAndNotEmpty, getDestinationExternalID, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util'); const { getContents, hashUserField } = require('./util'); @@ -690,10 +689,6 @@ const batchEvents = (eventsChunk) => { return events; }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const trackResponseList = []; // list containing single track event in batched format const eventsChunk = []; // temporary variable to divide payload into chunks const errorRespList = []; diff --git a/src/v0/destinations/tiktok_ads_offline_events/transform.js b/src/v0/destinations/tiktok_ads_offline_events/transform.js index 945c31ea63..dbe9a06dd6 100644 --- a/src/v0/destinations/tiktok_ads_offline_events/transform.js +++ b/src/v0/destinations/tiktok_ads_offline_events/transform.js @@ -9,7 +9,6 @@ const { removeUndefinedAndNullValues, isDefinedAndNotNullAndNotEmpty, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getSuccessRespEvents, defaultBatchRequestConfig, @@ -199,11 +198,6 @@ const batchEvents = (eventChunksArray) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchErrorRespList = []; const eventChunksArray = []; const { destination } = inputs[0]; diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 0cc66b2d7a..1d952693f2 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -22,6 +22,7 @@ const { PlatformError, TransformationError, OAuthSecretError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const logger = require('../../logger'); const stats = require('../../util/stats'); @@ -482,16 +483,6 @@ const getSuccessRespEvents = ( destination, }); -// Router transformer -// Error responses -const getErrorRespEvents = (metadata, statusCode, error, statTags, batched = false) => ({ - metadata, - batched, - statusCode, - error, - statTags, -}); - // ======================================================================== // Error Message UTILITIES // ======================================================================== @@ -1661,7 +1652,7 @@ function getValidDynamicFormConfig( */ const checkInvalidRtTfEvents = (inputs) => { if (!Array.isArray(inputs) || inputs.length === 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); + const respEvents = getErrorRespEvents([], 400, 'Invalid event array'); return [respEvents]; } return []; @@ -1723,11 +1714,6 @@ const handleRtTfSingleEventError = (input, error, reqMetadata) => { * @returns */ const simpleProcessRouterDest = async (inputs, singleTfFunc, reqMetadata, processParams) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const respList = await Promise.all( inputs.map(async (input) => { try { @@ -1755,10 +1741,6 @@ const simpleProcessRouterDest = async (inputs, singleTfFunc, reqMetadata, proces * @returns */ const simpleProcessRouterDestSync = async (inputs, singleTfFunc, reqMetadata, processParams) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const respList = []; // eslint-disable-next-line no-restricted-syntax for (const input of inputs) { @@ -2254,7 +2236,6 @@ module.exports = { getDestinationExternalIDInfoForRetl, getDestinationExternalIDObjectForRetl, getDeviceModel, - getErrorRespEvents, getEventTime, getFieldValueFromMessage, getFirstAndLastName, diff --git a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts index 62ee03c46d..dff0f772d3 100644 --- a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts @@ -1,248 +1,347 @@ -export const data = [ +const events = [ + { + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 1, + userId: 'u1', + }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '11', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + phone: '912382193', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'UK', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { name: '', version: '' }, + screen: { density: 2 }, + }, + event: 'Page View', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + adjustedValue: '10', + currency: 'INR', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + partialFailure: true, + campaignId: '1', + templateId: '0', + order_id: 10000, + total: 1000, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + price: '19', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + quantity: '2', + }, + { + product_id: '507f1f77bcf86cd7994390112', + sku: '45790-322', + name: 'Monopoly: 3rd Edition2', + price: '192', + quantity: 22, + position: '12', + category: 'Cars2', + url: 'https://www.example.com/product/path2', + image_url: 'https://www.example.com/product/path.jpg2', + }, + ], + }, + integrations: { All: true }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + }, + { + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 2, + userId: 'u1', + }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + type: 'identify', + traits: { status: 'elizabeth' }, + userId: 'emrichardson820+22822@gmail.com', + channel: 'sources', + context: { + sources: { + job_id: '24c5HJxHomh6YCngEOCgjS5r1KX/Syncher', + task_id: 'vw_rs_mailchimp_mocked_hg_data', + version: 'v1.8.1', + batch_id: 'f252c69d-c40d-450e-bcd2-2cf26cb62762', + job_run_id: 'c8el40l6e87v0c4hkbl0', + task_run_id: 'c8el40l6e87v0c4hkblg', + }, + externalId: [ + { + id: 'emrichardson820+22822@gmail.com', + type: 'MAILCHIMP-92e1f1ad2c', + identifierType: 'email_address', + }, + ], + mappedToDestination: 'true', + }, + recordId: '1', + rudderId: '4d5d0ed0-9db8-41cc-9bb0-a032f6bfa97a', + messageId: 'b3bee036-fc26-4f6d-9867-c17f85708a82', + }, + }, + { + metadata: { secret: {}, jobId: 3, userId: 'u1' }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '11', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + phone: '912382193', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'UK', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { name: '', version: '' }, + screen: { density: 2 }, + }, + event: 'Page View', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + adjustedValue: '10', + currency: 'INR', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + partialFailure: true, + campaignId: '1', + templateId: '0', + order_id: 10000, + total: 1000, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + price: '19', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + quantity: '2', + }, + { + product_id: '507f1f77bcf86cd7994390112', + sku: '45790-322', + name: 'Monopoly: 3rd Edition2', + price: '192', + quantity: 22, + position: '12', + category: 'Cars2', + url: 'https://www.example.com/product/path2', + image_url: 'https://www.example.com/product/path.jpg2', + }, + ], + }, + integrations: { All: true }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + }, +]; + +const invalidRtTfCases = [ { name: 'google_adwords_enhanced_conversions', - description: 'Test 0', + description: 'Test 1 - should abort events, invalid router transform structure', feature: 'router', module: 'destination', version: 'v0', input: { request: { body: { - input: [ + input: events[0], + destType: 'google_adwords_enhanced_conversions', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { - secret: { - access_token: 'abcd1234', - refresh_token: 'efgh5678', - developer_token: 'ijkl91011', - }, - jobId: 1, - userId: 'u1', - }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '11', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - channel: 'web', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.0', - }, - traits: { - phone: '912382193', - firstName: 'John', - lastName: 'Gomes', - city: 'London', - state: 'UK', - streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', - }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - locale: 'en-US', - ip: '0.0.0.0', - os: { name: '', version: '' }, - screen: { density: 2 }, - }, - event: 'Page View', - type: 'track', - messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', - originalTimestamp: '2019-10-14T11:15:18.299Z', - anonymousId: '00000000000000000000000000', - userId: '12345', - properties: { - gclid: 'gclid1234', - conversionDateTime: '2022-01-01 12:32:45-08:00', - adjustedValue: '10', - currency: 'INR', - adjustmentDateTime: '2022-01-01 12:32:45-08:00', - partialFailure: true, - campaignId: '1', - templateId: '0', - order_id: 10000, - total: 1000, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - price: '19', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - quantity: '2', - }, - { - product_id: '507f1f77bcf86cd7994390112', - sku: '45790-322', - name: 'Monopoly: 3rd Edition2', - price: '192', - quantity: 22, - position: '12', - category: 'Cars2', - url: 'https://www.example.com/product/path2', - image_url: 'https://www.example.com/product/path.jpg2', - }, - ], + error: 'Invalid event array', + metadata: [ + { + destType: 'google_adwords_enhanced_conversions', }, - integrations: { All: true }, - name: 'ApplicationLoaded', - sentAt: '2019-10-14T11:15:53.296Z', - }, + ], + batched: false, + statusCode: 400, }, + ], + }, + }, + }, + }, + { + name: 'google_adwords_enhanced_conversions', + description: + 'Test 2 - should abort events, invalid router transform structure without destType in payload & empty object as input', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: {}, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { - secret: { - access_token: 'abcd1234', - refresh_token: 'efgh5678', - developer_token: 'ijkl91011', - }, - jobId: 2, - userId: 'u1', - }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - type: 'identify', - traits: { status: 'elizabeth' }, - userId: 'emrichardson820+22822@gmail.com', - channel: 'sources', - context: { - sources: { - job_id: '24c5HJxHomh6YCngEOCgjS5r1KX/Syncher', - task_id: 'vw_rs_mailchimp_mocked_hg_data', - version: 'v1.8.1', - batch_id: 'f252c69d-c40d-450e-bcd2-2cf26cb62762', - job_run_id: 'c8el40l6e87v0c4hkbl0', - task_run_id: 'c8el40l6e87v0c4hkblg', - }, - externalId: [ - { - id: 'emrichardson820+22822@gmail.com', - type: 'MAILCHIMP-92e1f1ad2c', - identifierType: 'email_address', - }, - ], - mappedToDestination: 'true', + error: 'Invalid event array', + metadata: [ + { + destType: undefined, }, - recordId: '1', - rudderId: '4d5d0ed0-9db8-41cc-9bb0-a032f6bfa97a', - messageId: 'b3bee036-fc26-4f6d-9867-c17f85708a82', - }, + ], + batched: false, + statusCode: 400, }, + ], + }, + }, + }, + }, + { + name: 'google_adwords_enhanced_conversions', + description: + 'Test 3 - should abort events, invalid router transform structure without input & destType', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: {}, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { secret: {}, jobId: 3, userId: 'u1' }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '11', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - channel: 'web', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.0', - }, - traits: { - phone: '912382193', - firstName: 'John', - lastName: 'Gomes', - city: 'London', - state: 'UK', - streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', - }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - locale: 'en-US', - ip: '0.0.0.0', - os: { name: '', version: '' }, - screen: { density: 2 }, - }, - event: 'Page View', - type: 'track', - messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', - originalTimestamp: '2019-10-14T11:15:18.299Z', - anonymousId: '00000000000000000000000000', - userId: '12345', - properties: { - gclid: 'gclid1234', - conversionDateTime: '2022-01-01 12:32:45-08:00', - adjustedValue: '10', - currency: 'INR', - adjustmentDateTime: '2022-01-01 12:32:45-08:00', - partialFailure: true, - campaignId: '1', - templateId: '0', - order_id: 10000, - total: 1000, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - price: '19', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - quantity: '2', - }, - { - product_id: '507f1f77bcf86cd7994390112', - sku: '45790-322', - name: 'Monopoly: 3rd Edition2', - price: '192', - quantity: 22, - position: '12', - category: 'Cars2', - url: 'https://www.example.com/product/path2', - image_url: 'https://www.example.com/product/path.jpg2', - }, - ], + error: 'Invalid event array', + metadata: [ + { + destType: undefined, }, - integrations: { All: true }, - name: 'ApplicationLoaded', - sentAt: '2019-10-14T11:15:53.296Z', - }, + ], + batched: false, + statusCode: 400, }, ], + }, + }, + }, + }, +]; + +export const data = [ + { + name: 'google_adwords_enhanced_conversions', + description: 'Test 0', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: events, destType: 'google_adwords_enhanced_conversions', }, method: 'POST', @@ -405,4 +504,5 @@ export const data = [ }, }, }, + ...invalidRtTfCases, ]; From 108cbbabb86fdea44e604c92b5fcc8d688d64e89 Mon Sep 17 00:00:00 2001 From: Sankeerth Date: Mon, 26 Feb 2024 20:01:34 +0530 Subject: [PATCH 32/33] chore: formatting changes (#3002) --- .eslintrc.json | 4 +- .github/workflows/build-push-docker-image.yml | 2 +- .github/workflows/commitlint.yml | 36 + .github/workflows/component-test-report.yml | 4 +- .../workflows/prepare-for-staging-deploy.yml | 1 - .github/workflows/verify.yml | 36 + .prettierignore | 1 + .vscode/settings.json | 24 + package-lock.json | 91 +- package.json | 7 +- src/adapters/network.js | 4 +- src/adapters/networkHandlerFactory.js | 5 +- .../bingads_audience/procWorkflow.yaml | 2 +- .../destinations/bluecore/procWorkflow.yaml | 2 +- .../destinations/fullstory/procWorkflow.yaml | 13 +- .../v2/destinations/gladly/procWorkflow.yaml | 1 - .../v2/destinations/gladly/rtWorkflow.yaml | 2 +- .../v2/destinations/heap/procWorkflow.yaml | 2 - .../destinations/intercom/procWorkflow.yaml | 4 +- .../v2/destinations/intercom/rtWorkflow.yaml | 2 +- .../v2/destinations/kochava/procWorkflow.yaml | 1 - .../v2/destinations/lytics/procWorkflow.yaml | 3 +- .../pinterest_tag/procWorkflow.yaml | 2 +- .../pinterest_tag/rtWorkflow.yaml | 2 +- src/cdk/v2/destinations/rakuten/utils.js | 2 +- .../v2/destinations/reddit/procWorkflow.yaml | 2 +- .../v2/destinations/sprig/procWorkflow.yaml | 2 - .../v2/destinations/statsig/procWorkflow.yaml | 1 - .../tiktok_audience/procWorkflow.yaml | 2 - .../v2/destinations/zapier/procWorkflow.yaml | 1 - src/controllers/obs.delivery.js | 14 +- src/controllers/util/index.ts | 2 +- src/services/destination/nativeIntegration.ts | 2 +- src/services/userTransform.ts | 2 +- src/util/customTransformer-v1.js | 8 +- src/util/customTransformer.js | 6 +- src/util/customTransformerFactory.js | 24 +- src/util/error-extractor/index.ts | 34 +- src/util/error-extractor/types.ts | 2 +- src/util/ivmFactory.js | 10 +- src/util/prometheus.js | 10 +- src/util/stats.js | 8 +- src/v0/destinations/adobe_analytics/utils.js | 2 +- src/v0/destinations/af/transform.js | 7 +- src/v0/destinations/am/config.js | 2 +- .../data/TrackAddStoreConversionsConfig.json | 12 +- .../utils.js | 2 +- src/v0/destinations/marketo/networkHandler.js | 2 +- .../marketo_static_list/networkHandler.js | 2 +- .../tiktok_ads_offline_events/config.js | 22 +- src/v0/destinations/twitter_ads/config.js | 2 +- src/v0/destinations/twitter_ads/util.js | 29 +- src/v0/destinations/wootric/util.js | 4 +- src/v0/sources/formsort/transform.js | 18 +- src/v0/sources/formsort/transform.test.js | 95 +- src/v0/sources/shopify/shopify.util.test.js | 4 +- src/warehouse/index.js | 2 +- src/warehouse/util.js | 2 +- .../data/sources/shopify/response.json | 2 +- .../data/customerio_source_input.json | 6 +- .../data/customerio_source_output.json | 2 +- test/__tests__/data/formsort_source.json | 178 ++-- test/__tests__/data/proxy_input.json | 2 +- test/__tests__/data/shopify.json | 90 +- .../destinations/adj/processor/data.ts | 49 +- .../destinations/clevertap/network.ts | 3 +- .../destinations/clickup/network.ts | 486 ++++----- .../destinations/custify/deleteUsers/data.ts | 6 +- .../destinations/delighted/network.ts | 54 +- .../fb_custom_audience/network.ts | 7 +- .../destinations/freshmarketer/network.ts | 956 +++++++++--------- .../destinations/freshsales/network.ts | 955 ++++++++--------- .../destinations/gainsight/network.ts | 124 +-- .../destinations/gainsight_px/network.ts | 420 ++++---- .../destinations/iterable/deleteUsers/data.ts | 186 ++++ .../destinations/iterable/network.ts | 109 ++ .../destinations/klaviyo/network.ts | 128 ++- .../destinations/wootric/network.ts | 347 ++++--- tsconfig.json | 6 +- 79 files changed, 2561 insertions(+), 2145 deletions(-) create mode 100644 .github/workflows/commitlint.yml create mode 100644 .github/workflows/verify.yml create mode 100644 .vscode/settings.json create mode 100644 test/integrations/destinations/iterable/deleteUsers/data.ts create mode 100644 test/integrations/destinations/iterable/network.ts diff --git a/.eslintrc.json b/.eslintrc.json index d2928e50fd..7258c5c536 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,9 +9,9 @@ "airbnb-base", "airbnb-typescript/base", "plugin:sonarjs/recommended", - "prettier", "plugin:json/recommended", - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", + "prettier" ], "plugins": ["@typescript-eslint", "unicorn"], "globals": {}, diff --git a/.github/workflows/build-push-docker-image.yml b/.github/workflows/build-push-docker-image.yml index 9f6709a040..7ddae0a3ae 100644 --- a/.github/workflows/build-push-docker-image.yml +++ b/.github/workflows/build-push-docker-image.yml @@ -155,7 +155,7 @@ jobs: if: ${{ inputs.build_type == 'dt' }} run: | docker buildx imagetools create -t rudderstack/rudder-transformer:latest ${{ inputs.push_tags }}-amd64 ${{ inputs.push_tags }}-arm64 - + - name: Create latest ut multi-arch manifest # To be triggered only for release/hotfix PR merges coming from `prepare-for-prod-ut-deploy.yaml` if: ${{ inputs.build_type == 'ut' }} diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000000..a8ff39eee0 --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,36 @@ +name: Commitlint + +on: [push] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4.0.1 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Print versions + run: | + git --version + node --version + npm --version + npx commitlint --version + + # Run the commitlint action, considering its own dependencies and yours as well 🚀 + # `github.workspace` is the path to your repository. + - uses: wagoid/commitlint-github-action@v5 + env: + NODE_PATH: ${{ github.workspace }}/node_modules + with: + commitDepth: 1 diff --git a/.github/workflows/component-test-report.yml b/.github/workflows/component-test-report.yml index e41ca0a723..3d457df9ff 100644 --- a/.github/workflows/component-test-report.yml +++ b/.github/workflows/component-test-report.yml @@ -43,7 +43,7 @@ jobs: - name: Uplaod Report to S3 run: | aws s3 cp ./test_reports/ s3://test-integrations-dev/integrations-test-reports/rudder-transformer/${{ github.event.number }}/ --recursive - + - name: Add Test Report Link as Comment on PR uses: actions/github-script@v7 with: @@ -75,5 +75,3 @@ jobs: issue_number: prNumber, body: commentBody }); - - \ No newline at end of file diff --git a/.github/workflows/prepare-for-staging-deploy.yml b/.github/workflows/prepare-for-staging-deploy.yml index 4e8f29cffa..e7df8c43a5 100644 --- a/.github/workflows/prepare-for-staging-deploy.yml +++ b/.github/workflows/prepare-for-staging-deploy.yml @@ -68,7 +68,6 @@ jobs: secrets: DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} - create-pull-request: name: Update Helm Charts For Staging and Create Pull Request runs-on: ubuntu-latest diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000000..4caef8dd91 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,36 @@ +name: Verify + +on: + pull_request: + +jobs: + formatting-lint: + name: Check for formatting & lint errors + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + # Make sure the actual branch is checked out when running on pull requests + ref: ${{ github.head_ref }} + + - name: Setup Node + uses: actions/setup-node@v3.7.0 + with: + node-version-file: .nvmrc + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Run Lint Checks + run: | + npm run lint + + - run: git diff --exit-code + + - name: Error message + if: ${{ failure() }} + run: | + echo 'Eslint check is failing Ensure you have run `npm run lint` and committed the files locally.' diff --git a/.prettierignore b/.prettierignore index 93eb370b0d..99747b29bb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,4 @@ test/**/*.js !test/**/data.js src/util/lodash-es-core.js src/util/url-search-params.min.js +dist diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..13c49c08f1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,24 @@ +{ + "prettier.requireConfig": true, + "prettier.configPath": ".prettierrc", + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "editor.codeActionsOnSave": { + "source.organizeImports": "never" + }, + "eslint.validate": ["javascript", "typescript"] +} diff --git a/package-lock.json b/package-lock.json index 153851dab2..0f44dfce3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,9 +91,10 @@ "eslint": "^8.40.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-prettier": "^8.8.0", + "eslint-config-prettier": "^8.10.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-json": "^3.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-sonarjs": "^0.19.0", "eslint-plugin-unicorn": "^46.0.1", "glob": "^10.3.3", @@ -106,7 +107,7 @@ "madge": "^6.1.0", "mocked-env": "^1.3.5", "node-notifier": "^10.0.1", - "prettier": "^2.8.8", + "prettier": "^3.2.4", "semver": "^7.5.3", "standard-version": "^9.5.0", "supertest": "^6.3.3", @@ -4376,6 +4377,18 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -9684,6 +9697,36 @@ "node": ">=12.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-sonarjs": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.19.0.tgz", @@ -10157,6 +10200,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -17736,20 +17785,32 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -19723,6 +19784,22 @@ "node": ">=10" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/package.json b/package.json index 455819a3b7..f6ab6bc1dd 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,9 @@ "setup": "npm ci", "setup:swagger": "swagger-cli bundle swagger/api.yaml --outfile dist/swagger.json --type json", "format": "prettier --write .", - "lint": "eslint . || exit 0", "lint:fix": "eslint . --fix", "lint:fix:json": "eslint --ext .json --fix .", + "lint": "npm run format && npm run lint:fix", "check:merge": "npm run verify || exit 1; codecov", "start": "cd dist;node ./src/index.js;cd ..", "build:start": "npm run build && npm run start", @@ -136,9 +136,10 @@ "eslint": "^8.40.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-prettier": "^8.8.0", + "eslint-config-prettier": "^8.10.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-json": "^3.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-sonarjs": "^0.19.0", "eslint-plugin-unicorn": "^46.0.1", "glob": "^10.3.3", @@ -151,7 +152,7 @@ "madge": "^6.1.0", "mocked-env": "^1.3.5", "node-notifier": "^10.0.1", - "prettier": "^2.8.8", + "prettier": "^3.2.4", "semver": "^7.5.3", "standard-version": "^9.5.0", "supertest": "^6.3.3", diff --git a/src/adapters/network.js b/src/adapters/network.js index d759412b7a..0720638d12 100644 --- a/src/adapters/network.js +++ b/src/adapters/network.js @@ -57,7 +57,7 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => { destType, endpointPath, requestMethod, - module + module, }); stats.counter('outgoing_request_count', 1, { feature, @@ -66,7 +66,7 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => { success: clientResponse.success, statusCode, requestMethod, - module + module, }); }; diff --git a/src/adapters/networkHandlerFactory.js b/src/adapters/networkHandlerFactory.js index e8c3748d15..de80809a04 100644 --- a/src/adapters/networkHandlerFactory.js +++ b/src/adapters/networkHandlerFactory.js @@ -27,8 +27,9 @@ SUPPORTED_VERSIONS.forEach((version) => { // }, // generic: GenericNetworkHandler, // } - handlers[version][dest] = - require(`../${version}/destinations/${dest}/networkHandler`).networkHandler; + handlers[version][dest] = require( + `../${version}/destinations/${dest}/networkHandler`, + ).networkHandler; } catch { // Do nothing as exception indicates // network handler is not defined for that destination diff --git a/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml b/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml index 744c05a9a6..3292d66c69 100644 --- a/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml +++ b/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml @@ -50,4 +50,4 @@ steps: const response = $.defaultRequestConfig(); response.body.JSON = payload; response - ) \ No newline at end of file + ) diff --git a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml index 378659fa2a..480bced699 100644 --- a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml +++ b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml @@ -54,7 +54,7 @@ steps: $.verifyPayload(newPayload, ^.message); $.removeUndefinedNullValuesAndEmptyObjectArray(newPayload) )[]; - + - name: buildResponse template: | $.context.payloads.( diff --git a/src/cdk/v2/destinations/fullstory/procWorkflow.yaml b/src/cdk/v2/destinations/fullstory/procWorkflow.yaml index 50ac2a8163..1a54e8688c 100644 --- a/src/cdk/v2/destinations/fullstory/procWorkflow.yaml +++ b/src/cdk/v2/destinations/fullstory/procWorkflow.yaml @@ -5,7 +5,7 @@ bindings: exportAll: true - name: removeUndefinedAndNullValues path: ../../../../v0/util - + steps: - name: validateInput template: | @@ -28,7 +28,7 @@ steps: $.context.payload.uid = .message.userId; $.context.payload.email = .message.context.traits.email; $.context.payload.display_name = .message.context.traits.name; - + - name: trackPayload condition: $.context.messageType == "track" template: | @@ -42,9 +42,9 @@ steps: condition: $.context.messageType == "track" template: | $.assert(.message.event, "event is required for track call") - + - name: mapContextFieldsForTrack - condition: $.context.messageType == "track" + condition: $.context.messageType == "track" template: | $.context.payload.context.browser = { "url": .message.context.page.url, @@ -67,7 +67,7 @@ steps: "region": .message.properties.region, "country": .message.properties.country, }; - + - name: mapIdsForTrack condition: $.context.messageType == "track" template: | @@ -99,6 +99,3 @@ steps: "params": {}, "files": {} }) - - - diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index fe8697bc31..a53a0ca8f5 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -13,7 +13,6 @@ bindings: path: ../../../../adapters/network - name: processAxiosResponse path: ../../../../adapters/utils/networkUtils - steps: - name: checkIfProcessed diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml index 341e5552c8..fc5d474d60 100644 --- a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml @@ -30,4 +30,4 @@ steps: )[] - name: finalPayload template: | - [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] diff --git a/src/cdk/v2/destinations/heap/procWorkflow.yaml b/src/cdk/v2/destinations/heap/procWorkflow.yaml index 0191b75d18..8326a61a79 100644 --- a/src/cdk/v2/destinations/heap/procWorkflow.yaml +++ b/src/cdk/v2/destinations/heap/procWorkflow.yaml @@ -58,5 +58,3 @@ steps: "Content-Type": "application/json" }; response - - \ No newline at end of file diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml index 4b2ca1869e..0a8842d5e7 100644 --- a/src/cdk/v2/destinations/intercom/procWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml @@ -71,7 +71,7 @@ steps: payload; - name: identifyPayloadForLatestVersion - condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" template: | const payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.identifyTransformationForLatestVersion; payload.name = $.getName(.message); @@ -187,7 +187,7 @@ steps: template: | $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies"; - name: prepareFinalPayload - template: + template: | $.context.requestMethod = 'POST'; $.removeUndefinedAndNullValues($.context.payload); diff --git a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml index 3ed1046959..edb7267b84 100644 --- a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml @@ -30,4 +30,4 @@ steps: )[] - name: finalPayload template: | - [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] diff --git a/src/cdk/v2/destinations/kochava/procWorkflow.yaml b/src/cdk/v2/destinations/kochava/procWorkflow.yaml index 557b1a0b63..3e73ee1520 100644 --- a/src/cdk/v2/destinations/kochava/procWorkflow.yaml +++ b/src/cdk/v2/destinations/kochava/procWorkflow.yaml @@ -10,7 +10,6 @@ bindings: - path: ../../bindings/jsontemplate - path: ./config.js - steps: - name: validateInput template: | diff --git a/src/cdk/v2/destinations/lytics/procWorkflow.yaml b/src/cdk/v2/destinations/lytics/procWorkflow.yaml index 1d6177fe09..2622146221 100644 --- a/src/cdk/v2/destinations/lytics/procWorkflow.yaml +++ b/src/cdk/v2/destinations/lytics/procWorkflow.yaml @@ -45,7 +45,7 @@ steps: $.context.payload._e = .message.event; - name: pageOrScreenPayload condition: $.context.messageType === {{$.EventType.PAGE}} || - $.context.messageType === {{$.EventType.SCREEN}} + $.context.messageType === {{$.EventType.SCREEN}} template: | $.context.payload.event = .message.name - name: cleanPaylod @@ -66,4 +66,3 @@ steps: "Content-Type": "application/json" }; response - diff --git a/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml b/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml index 8a956e905c..1122a80404 100644 --- a/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml +++ b/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml @@ -246,4 +246,4 @@ steps: }, "params": $.outputs.checkSendTestEventConfig, "files": {} - })[] \ No newline at end of file + })[] diff --git a/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml b/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml index 227942dfea..215ead12b1 100644 --- a/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml @@ -12,7 +12,7 @@ steps: If sendTestEvent is enabled, we send test event to the destination ref: https://help.pinterest.com/en/business/article/track-conversions-with-the-conversions-api template: | - ^[0].destination.Config.sendAsTestEvent ? {"test": true} : {} + ^[0].destination.Config.sendAsTestEvent ? {"test": true} : {} - name: transform externalWorkflow: diff --git a/src/cdk/v2/destinations/rakuten/utils.js b/src/cdk/v2/destinations/rakuten/utils.js index fe37455a57..ef6b197db7 100644 --- a/src/cdk/v2/destinations/rakuten/utils.js +++ b/src/cdk/v2/destinations/rakuten/utils.js @@ -59,7 +59,7 @@ const constructLineItems = (properties) => { } if (product.price) { - return product.quantity * product.price * 100; + return product.quantity * product.price * 100; } return product.amount * 100; }); diff --git a/src/cdk/v2/destinations/reddit/procWorkflow.yaml b/src/cdk/v2/destinations/reddit/procWorkflow.yaml index 1cf195707d..59725c1257 100644 --- a/src/cdk/v2/destinations/reddit/procWorkflow.yaml +++ b/src/cdk/v2/destinations/reddit/procWorkflow.yaml @@ -57,7 +57,7 @@ steps: - name: customFields condition: $.outputs.prepareTrackPayload.eventType.tracking_type === "Purchase" - reference: "https://ads-api.reddit.com/docs/v2/#tag/Conversions/paths/~1api~1v2.0~1conversions~1events~1%7Baccount_id%7D/post" + reference: 'https://ads-api.reddit.com/docs/v2/#tag/Conversions/paths/~1api~1v2.0~1conversions~1events~1%7Baccount_id%7D/post' template: | const revenue_in_cents = .message.properties.revenue ? Math.round(Number(.message.properties.revenue)*100) const customFields = .message.().({ diff --git a/src/cdk/v2/destinations/sprig/procWorkflow.yaml b/src/cdk/v2/destinations/sprig/procWorkflow.yaml index 18b46913fd..4dcebeffcd 100644 --- a/src/cdk/v2/destinations/sprig/procWorkflow.yaml +++ b/src/cdk/v2/destinations/sprig/procWorkflow.yaml @@ -69,5 +69,3 @@ steps: "authorization": "API-Key " + .destination.Config.apiKey }; response - - diff --git a/src/cdk/v2/destinations/statsig/procWorkflow.yaml b/src/cdk/v2/destinations/statsig/procWorkflow.yaml index b3c85e31dc..6d3328b87e 100644 --- a/src/cdk/v2/destinations/statsig/procWorkflow.yaml +++ b/src/cdk/v2/destinations/statsig/procWorkflow.yaml @@ -26,4 +26,3 @@ steps: "content-type": "application/json" }; response - diff --git a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml index cd84ecbc87..5862fbf372 100644 --- a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml +++ b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml @@ -1,4 +1,3 @@ - bindings: - name: EventType path: ../../../../constants @@ -48,7 +47,6 @@ steps: "action": $.ACTION_MAP[action], })[] - - name: buildResponseForProcessTransformation description: build response template: | diff --git a/src/cdk/v2/destinations/zapier/procWorkflow.yaml b/src/cdk/v2/destinations/zapier/procWorkflow.yaml index 82d15cdec0..9f1512836e 100644 --- a/src/cdk/v2/destinations/zapier/procWorkflow.yaml +++ b/src/cdk/v2/destinations/zapier/procWorkflow.yaml @@ -44,4 +44,3 @@ steps: "content-type": "application/json" }; response - diff --git a/src/controllers/obs.delivery.js b/src/controllers/obs.delivery.js index 4a93afe1dc..5aa3ca5862 100644 --- a/src/controllers/obs.delivery.js +++ b/src/controllers/obs.delivery.js @@ -96,11 +96,15 @@ const DestProxyController = { destination, }); - response = generateErrorObject(err, { - [tags.TAG_NAMES.DEST_TYPE]: destination.toUpperCase(), - [tags.TAG_NAMES.MODULE]: tags.MODULES.DESTINATION, - [tags.TAG_NAMES.FEATURE]: tags.FEATURES.DATA_DELIVERY, - }, false); + response = generateErrorObject( + err, + { + [tags.TAG_NAMES.DEST_TYPE]: destination.toUpperCase(), + [tags.TAG_NAMES.MODULE]: tags.MODULES.DESTINATION, + [tags.TAG_NAMES.FEATURE]: tags.FEATURES.DATA_DELIVERY, + }, + false, + ); response.message = `[TransformerProxyTest] Error occurred while testing proxy for destination ("${destination}"): "${err.message}"`; logger.error(response.message); logger.error(err); diff --git a/src/controllers/util/index.ts b/src/controllers/util/index.ts index 75d3d8ffa7..c5bf7ab358 100644 --- a/src/controllers/util/index.ts +++ b/src/controllers/util/index.ts @@ -51,7 +51,7 @@ export class ControllerUtility { private static convertSourceInputv0Tov1(sourceEvents: unknown[]): SourceInput[] { return sourceEvents.map( - (sourceEvent) => ({ event: sourceEvent, source: undefined } as SourceInput), + (sourceEvent) => ({ event: sourceEvent, source: undefined }) as SourceInput, ); } diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts index 2dd78b58e2..c33772d01d 100644 --- a/src/services/destination/nativeIntegration.ts +++ b/src/services/destination/nativeIntegration.ts @@ -212,7 +212,7 @@ export class NativeIntegrationDestinationService implements DestinationService { error: JSON.stringify(v0Response.destinationResponse?.response), statusCode: v0Response.status, metadata, - } as DeliveryJobState), + }) as DeliveryJobState, ); responseProxy = { response: jobStates, diff --git a/src/services/userTransform.ts b/src/services/userTransform.ts index bf34e3d82a..bae833c86a 100644 --- a/src/services/userTransform.ts +++ b/src/services/userTransform.ts @@ -158,7 +158,7 @@ export class UserTransformService { statusCode: status, metadata: e.metadata, error: errorString, - } as ProcessorTransformationResponse), + }) as ProcessorTransformationResponse, ), ); stats.counter('user_transform_errors', eventsToProcess.length, { diff --git a/src/util/customTransformer-v1.js b/src/util/customTransformer-v1.js index 60f8e493fa..7e854a3714 100644 --- a/src/util/customTransformer-v1.js +++ b/src/util/customTransformer-v1.js @@ -55,7 +55,7 @@ async function userTransformHandlerV1( testMode = false, ) { if (!userTransformation.versionId) { - return { transformedEvents : events }; + return { transformedEvents: events }; } const isolatevmFactory = await getFactory( @@ -88,9 +88,9 @@ async function userTransformHandlerV1( const tags = { identifier: 'v1', errored: transformationError ? true : false, - ...events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}, - ...events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {} - } + ...(events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}), + ...(events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}), + }; stats.counter('user_transform_function_input_events', events.length, tags); stats.timing('user_transform_function_latency', invokeTime, tags); } diff --git a/src/util/customTransformer.js b/src/util/customTransformer.js index 001fe3216c..a87c12dd6e 100644 --- a/src/util/customTransformer.js +++ b/src/util/customTransformer.js @@ -254,9 +254,9 @@ async function runUserTransform( const tags = { identifier: 'v0', errored: transformationError ? true : false, - ...events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}, - ...events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {} - } + ...(events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}), + ...(events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}), + }; stats.counter('user_transform_function_input_events', events.length, tags); stats.timing('user_transform_function_latency', invokeTime, tags); diff --git a/src/util/customTransformerFactory.js b/src/util/customTransformerFactory.js index 1bf10e5d45..ee53531946 100644 --- a/src/util/customTransformerFactory.js +++ b/src/util/customTransformerFactory.js @@ -1,12 +1,6 @@ -const { - setOpenFaasUserTransform, - runOpenFaasUserTransform, -} = require('./customTransformer-faas'); +const { setOpenFaasUserTransform, runOpenFaasUserTransform } = require('./customTransformer-faas'); -const { - userTransformHandlerV1, - setUserTransformHandlerV1, -} = require('./customTransformer-v1'); +const { userTransformHandlerV1, setUserTransformHandlerV1 } = require('./customTransformer-v1'); const UserTransformHandlerFactory = (userTransformation) => { return { @@ -23,20 +17,10 @@ const UserTransformHandlerFactory = (userTransformation) => { switch (userTransformation.language) { case 'pythonfaas': case 'python': - return runOpenFaasUserTransform( - events, - userTransformation, - libraryVersionIds, - testMode - ); + return runOpenFaasUserTransform(events, userTransformation, libraryVersionIds, testMode); default: - return userTransformHandlerV1( - events, - userTransformation, - libraryVersionIds, - testMode - ); + return userTransformHandlerV1(events, userTransformation, libraryVersionIds, testMode); } }, }; diff --git a/src/util/error-extractor/index.ts b/src/util/error-extractor/index.ts index 68ebac9aca..6ff374b869 100644 --- a/src/util/error-extractor/index.ts +++ b/src/util/error-extractor/index.ts @@ -1,19 +1,18 @@ /* eslint-disable max-classes-per-file */ -import { MessageDetails, StatusCode, Stat } from "./types"; +import { MessageDetails, StatusCode, Stat } from './types'; export class ErrorDetailsExtractor { status: StatusCode; messageDetails: MessageDetails; - stat : Stat + stat: Stat; - constructor (builder: ErrorDetailsExtractorBuilder) { + constructor(builder: ErrorDetailsExtractorBuilder) { this.status = builder.getStatus(); this.messageDetails = builder.getMessageDetails(); this.stat = builder.getStat(); } - } export class ErrorDetailsExtractorBuilder { @@ -22,27 +21,28 @@ export class ErrorDetailsExtractorBuilder { messageDetails: MessageDetails; stat: Stat; + constructor() { this.status = 0; this.messageDetails = {}; this.stat = {}; } - + setStatus(status: number): ErrorDetailsExtractorBuilder { this.status = status; return this; } setStat(stat: Record): ErrorDetailsExtractorBuilder { - this.stat = stat + this.stat = stat; return this; } /** * This means we need to set a message from a specific field that we see from the destination's response - * + * * @param {string} fieldPath -- Path of the field which should be set as "error message" - * @returns + * @returns */ setMessageField(fieldPath: string): ErrorDetailsExtractorBuilder { if (this.messageDetails?.message) { @@ -50,16 +50,16 @@ export class ErrorDetailsExtractorBuilder { return this; } this.messageDetails = { - field: fieldPath - } + field: fieldPath, + }; return this; } /** * This means we need to set the message provided - * + * * @param {string} msg - error message - * @returns + * @returns */ setMessage(msg: string): ErrorDetailsExtractorBuilder { if (this.messageDetails?.field) { @@ -67,13 +67,13 @@ export class ErrorDetailsExtractorBuilder { return this; } this.messageDetails = { - message: msg - } + message: msg, + }; return this; } build(): ErrorDetailsExtractor { - return new ErrorDetailsExtractor(this) + return new ErrorDetailsExtractor(this); } getStatus(): number { @@ -83,10 +83,8 @@ export class ErrorDetailsExtractorBuilder { getStat(): Record { return this.stat; } - + getMessageDetails(): Record { return this.messageDetails; } } - - diff --git a/src/util/error-extractor/types.ts b/src/util/error-extractor/types.ts index ff7290b4ff..b93d2fafe5 100644 --- a/src/util/error-extractor/types.ts +++ b/src/util/error-extractor/types.ts @@ -1,3 +1,3 @@ export type MessageDetails = Record; export type StatusCode = number; -export type Stat = Record \ No newline at end of file +export type Stat = Record; diff --git a/src/util/ivmFactory.js b/src/util/ivmFactory.js index 2ab5f9548a..9a6419295d 100644 --- a/src/util/ivmFactory.js +++ b/src/util/ivmFactory.js @@ -23,7 +23,9 @@ async function evaluateModule(isolate, context, moduleCode) { } async function loadModule(isolateInternal, contextInternal, moduleName, moduleCode) { - const module = await isolateInternal.compileModule(moduleCode, { filename: `library ${moduleName}` }); + const module = await isolateInternal.compileModule(moduleCode, { + filename: `library ${moduleName}`, + }); await module.instantiate(contextInternal, () => {}); return module; } @@ -256,7 +258,7 @@ async function createIvm(code, libraryVersionIds, versionId, secrets, testMode) } }); - await jail.set('extractStackTrace', function(trace, stringLiterals) { + await jail.set('extractStackTrace', function (trace, stringLiterals) { return extractStackTraceUptoLastSubstringMatch(trace, stringLiterals); }); @@ -346,7 +348,9 @@ async function createIvm(code, libraryVersionIds, versionId, secrets, testMode) // Now we can execute the script we just compiled: const bootstrapScriptResult = await bootstrap.run(context); // const customScript = await isolate.compileScript(`${library} ;\n; ${code}`); - const customScriptModule = await isolate.compileModule(`${codeWithWrapper}`, { filename: 'base transformation' }); + const customScriptModule = await isolate.compileModule(`${codeWithWrapper}`, { + filename: 'base transformation', + }); await customScriptModule.instantiate(context, async (spec) => { if (librariesMap[spec]) { return compiledModules[spec].module; diff --git a/src/util/prometheus.js b/src/util/prometheus.js index 48868449c3..eec480bbff 100644 --- a/src/util/prometheus.js +++ b/src/util/prometheus.js @@ -533,7 +533,15 @@ class Prometheus { name: 'outgoing_request_count', help: 'Outgoing HTTP requests count', type: 'counter', - labelNames: ['feature', 'destType', 'endpointPath', 'success', 'statusCode', 'requestMethod' , 'module'], + labelNames: [ + 'feature', + 'destType', + 'endpointPath', + 'success', + 'statusCode', + 'requestMethod', + 'module', + ], }, // Gauges diff --git a/src/util/stats.js b/src/util/stats.js index e57ab85731..9a32fd1de3 100644 --- a/src/util/stats.js +++ b/src/util/stats.js @@ -13,17 +13,19 @@ function init() { switch (statsClientType) { case 'statsd': - logger.info("setting up statsd client") + logger.info('setting up statsd client'); statsClient = new statsd.Statsd(); break; case 'prometheus': - logger.info("setting up prometheus client") + logger.info('setting up prometheus client'); statsClient = new prometheus.Prometheus(); break; default: - logger.error(`invalid stats client type: ${statsClientType}, supported values are 'statsd' and 'prometheues'`) + logger.error( + `invalid stats client type: ${statsClientType}, supported values are 'statsd' and 'prometheues'`, + ); } } diff --git a/src/v0/destinations/adobe_analytics/utils.js b/src/v0/destinations/adobe_analytics/utils.js index 97dc6e90bb..ceba177ff1 100644 --- a/src/v0/destinations/adobe_analytics/utils.js +++ b/src/v0/destinations/adobe_analytics/utils.js @@ -93,7 +93,7 @@ function escapeToHTML(inputString) { '&': '&', '<': '<', '>': '>', - }[match]), + })[match], ); } diff --git a/src/v0/destinations/af/transform.js b/src/v0/destinations/af/transform.js index 57629b9483..d6c41937a1 100644 --- a/src/v0/destinations/af/transform.js +++ b/src/v0/destinations/af/transform.js @@ -113,7 +113,12 @@ function getEventValueForUnIdentifiedTrackEvent(message) { return { eventValue }; } -function getEventValueMapFromMappingJson(message, mappingJson, isMultiSupport, addPropertiesAtRoot) { +function getEventValueMapFromMappingJson( + message, + mappingJson, + isMultiSupport, + addPropertiesAtRoot, +) { let eventValue = {}; if (addPropertiesAtRoot) { diff --git a/src/v0/destinations/am/config.js b/src/v0/destinations/am/config.js index 3e51a67137..78f8d43e94 100644 --- a/src/v0/destinations/am/config.js +++ b/src/v0/destinations/am/config.js @@ -136,5 +136,5 @@ module.exports = { batchEventsWithUserIdLengthLowerThanFive, IDENTIFY_AM, AMBatchSizeLimit, - AMBatchEventLimit + AMBatchEventLimit, }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json b/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json index aeecc3e01b..9c88e59ddb 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json +++ b/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json @@ -1,10 +1,7 @@ [ { "destKey": "operations.create.transaction_attribute.store_attribute.store_code", - "sourceKeys": [ - "properties.store_code", - "properties.storeCode" - ], + "sourceKeys": ["properties.store_code", "properties.storeCode"], "required": false, "metadata": { "type": "toString" @@ -25,10 +22,7 @@ }, { "destKey": "operations.create.transaction_attribute.order_id", - "sourceKeys": [ - "properties.order_id", - "properties.orderId" - ], + "sourceKeys": ["properties.order_id", "properties.orderId"], "required": false, "metadata": { "type": "toString" @@ -52,4 +46,4 @@ ], "required": true } -] \ No newline at end of file +] diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 19989d0eaa..ee677373a3 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -65,7 +65,7 @@ const getConversionActionId = async (headers, params) => { feature: 'transformation', endpointPath: `/googleAds:searchStream`, requestMethod: 'POST', - module: 'dataDelivery' + module: 'dataDelivery', }); searchStreamResponse = processAxiosResponse(searchStreamResponse); if (!isHttpStatusSuccess(searchStreamResponse.status)) { diff --git a/src/v0/destinations/marketo/networkHandler.js b/src/v0/destinations/marketo/networkHandler.js index 1d4b316e8d..ac555accfe 100644 --- a/src/v0/destinations/marketo/networkHandler.js +++ b/src/v0/destinations/marketo/networkHandler.js @@ -4,7 +4,7 @@ const { proxyRequest, prepareProxyRequest } = require('../../../adapters/network const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const responseHandler = (responseParams) => { - const { destinationResponse, destType,rudderJobMetadata } = responseParams; + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; const { status } = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); diff --git a/src/v0/destinations/marketo_static_list/networkHandler.js b/src/v0/destinations/marketo_static_list/networkHandler.js index 9e73cd1f91..086378cf6a 100644 --- a/src/v0/destinations/marketo_static_list/networkHandler.js +++ b/src/v0/destinations/marketo_static_list/networkHandler.js @@ -7,7 +7,7 @@ const { DESTINATION } = require('./config'); const responseHandler = (responseParams) => { const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; - const { status} = destinationResponse; + const { status } = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); // check for marketo application level failures marketoResponseHandler( diff --git a/src/v0/destinations/tiktok_ads_offline_events/config.js b/src/v0/destinations/tiktok_ads_offline_events/config.js index 3c58b42a44..4bb3bda850 100644 --- a/src/v0/destinations/tiktok_ads_offline_events/config.js +++ b/src/v0/destinations/tiktok_ads_offline_events/config.js @@ -19,23 +19,23 @@ const CONFIG_CATEGORIES = { const PARTNER_NAME = 'RudderStack'; const EVENT_NAME_MAPPING = { - 'addpaymentinfo': 'AddPaymentInfo', - 'addtocart': 'AddToCart', - 'addtowishlist': 'AddToWishlist', + addpaymentinfo: 'AddPaymentInfo', + addtocart: 'AddToCart', + addtowishlist: 'AddToWishlist', 'checkout started': 'InitiateCheckout', 'checkout step completed': 'CompletePayment', - 'clickbutton': 'ClickButton', - 'completeregistration': 'CompleteRegistration', - 'contact': 'Contact', - 'download': 'Download', + clickbutton: 'ClickButton', + completeregistration: 'CompleteRegistration', + contact: 'Contact', + download: 'Download', 'order completed': 'PlaceAnOrder', 'payment info entered': 'AddPaymentInfo', 'product added': 'AddToCart', 'product added to wishlist': 'AddToWishlist', - 'search': 'Search', - 'submitform': 'SubmitForm', - 'subscribe': 'Subscribe', - 'viewcontent': 'ViewContent', + search: 'Search', + submitform: 'SubmitForm', + subscribe: 'Subscribe', + viewcontent: 'ViewContent', }; const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); diff --git a/src/v0/destinations/twitter_ads/config.js b/src/v0/destinations/twitter_ads/config.js index 601675fc2f..6b0db0622c 100644 --- a/src/v0/destinations/twitter_ads/config.js +++ b/src/v0/destinations/twitter_ads/config.js @@ -14,5 +14,5 @@ const mappingConfig = getMappingConfig(ConfigCategories, __dirname); module.exports = { mappingConfig, ConfigCategories, - BASE_URL + BASE_URL, }; diff --git a/src/v0/destinations/twitter_ads/util.js b/src/v0/destinations/twitter_ads/util.js index 2f237b1dd8..ad59a81267 100644 --- a/src/v0/destinations/twitter_ads/util.js +++ b/src/v0/destinations/twitter_ads/util.js @@ -2,23 +2,20 @@ const crypto = require('crypto'); const oauth1a = require('oauth-1.0a'); function getAuthHeaderForRequest(request, oAuthObject) { - const oauth = oauth1a({ - consumer: { key: oAuthObject.consumerKey, secret: oAuthObject.consumerSecret }, - signature_method: 'HMAC-SHA1', - hash_function(base_string, k) { - return crypto - .createHmac('sha1', k) - .update(base_string) - .digest('base64') - }, - }) + const oauth = oauth1a({ + consumer: { key: oAuthObject.consumerKey, secret: oAuthObject.consumerSecret }, + signature_method: 'HMAC-SHA1', + hash_function(base_string, k) { + return crypto.createHmac('sha1', k).update(base_string).digest('base64'); + }, + }); - const authorization = oauth.authorize(request, { - key: oAuthObject.accessToken, - secret: oAuthObject.accessTokenSecret, - }); + const authorization = oauth.authorize(request, { + key: oAuthObject.accessToken, + secret: oAuthObject.accessTokenSecret, + }); - return oauth.toHeader(authorization); + return oauth.toHeader(authorization); } -module.exports = { getAuthHeaderForRequest }; \ No newline at end of file +module.exports = { getAuthHeaderForRequest }; diff --git a/src/v0/destinations/wootric/util.js b/src/v0/destinations/wootric/util.js index 0ae0a4940b..c2505c635b 100644 --- a/src/v0/destinations/wootric/util.js +++ b/src/v0/destinations/wootric/util.js @@ -48,7 +48,7 @@ const getAccessToken = async (destination) => { feature: 'transformation', endpointPath: `/oauth/token`, requestMethod: 'POST', - module: 'router' + module: 'router', }); const processedAuthResponse = processAxiosResponse(wootricAuthResponse); // If the request fails, throwing error. @@ -103,7 +103,7 @@ const retrieveUserDetails = async (endUserId, externalId, accessToken) => { feature: 'transformation', endpointPath: `/v1/end_users/`, requestMethod: 'GET', - module: 'router' + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); diff --git a/src/v0/sources/formsort/transform.js b/src/v0/sources/formsort/transform.js index 18d7b8fc0e..dd37482bc4 100644 --- a/src/v0/sources/formsort/transform.js +++ b/src/v0/sources/formsort/transform.js @@ -1,18 +1,16 @@ -const path = require("path"); -const fs = require("fs"); -const { generateUUID, isDefinedAndNotNull } = require("../../util"); -const Message = require("../message"); +const path = require('path'); +const fs = require('fs'); +const { generateUUID, isDefinedAndNotNull } = require('../../util'); +const Message = require('../message'); // import mapping json using JSON.parse to preserve object key order -const mapping = JSON.parse( - fs.readFileSync(path.resolve(__dirname, "./mapping.json"), "utf-8") -); +const mapping = JSON.parse(fs.readFileSync(path.resolve(__dirname, './mapping.json'), 'utf-8')); function process(event) { const message = new Message(`Formsort`); // we are setting event type as track always - message.setEventType("track"); + message.setEventType('track'); message.setPropertiesV2(event, mapping); @@ -23,9 +21,9 @@ function process(event) { // setting event Name if (event.finalized) { - message.setEventName("FlowFinalized"); + message.setEventName('FlowFinalized'); } else { - message.setEventName("FlowLoaded"); + message.setEventName('FlowLoaded'); } return message; diff --git a/src/v0/sources/formsort/transform.test.js b/src/v0/sources/formsort/transform.test.js index 9b0d814d6a..e3d686fcef 100644 --- a/src/v0/sources/formsort/transform.test.js +++ b/src/v0/sources/formsort/transform.test.js @@ -1,52 +1,51 @@ const { process } = require('./transform'); it(`Transform.js Tests`, () => { - const data = { - input: { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": false, - "created_at": "2022-11-25T14:41:22+00:00" + const data = { + input: { + answers: { + yes: true, + enter_email: 'test@user.com', + enter_name: '2022-11-17', + yes_or_no: false, + }, + responder_uuid: '66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7', + flow_label: 'new-flow-2022-11-25', + variant_label: 'main', + variant_uuid: '0828efa7-7215-4e7d-a7ab-6c1079010cea', + finalized: false, + created_at: '2022-11-25T14:41:22+00:00', + }, + output: { + context: { + library: { + name: 'unknown', + version: 'unknown', }, - output: { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowLoaded" - } - }; - const output = process(data.input); - expect(output).toEqual(data.output); - -}); \ No newline at end of file + integration: { + name: 'Formsort', + }, + page: { + title: 'new-flow-2022-11-25', + }, + variantLabel: 'main', + variantUuid: '0828efa7-7215-4e7d-a7ab-6c1079010cea', + }, + integrations: { + Formsort: false, + }, + type: 'track', + userId: '66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7', + originalTimestamp: '2022-11-25T14:41:22+00:00', + properties: { + yes: true, + enter_email: 'test@user.com', + enter_name: '2022-11-17', + yes_or_no: false, + }, + event: 'FlowLoaded', + }, + }; + const output = process(data.input); + expect(output).toEqual(data.output); +}); diff --git a/src/v0/sources/shopify/shopify.util.test.js b/src/v0/sources/shopify/shopify.util.test.js index 9c570dde41..d058db36b5 100644 --- a/src/v0/sources/shopify/shopify.util.test.js +++ b/src/v0/sources/shopify/shopify.util.test.js @@ -1,5 +1,4 @@ -const { getShopifyTopic, -} = require('./util'); +const { getShopifyTopic } = require('./util'); jest.mock('ioredis', () => require('../../../../test/__mocks__/redis')); describe('Shopify Utils Test', () => { describe('Fetching Shopify Topic Test Cases', () => { @@ -58,5 +57,4 @@ describe('Shopify Utils Test', () => { } }); }); - }); diff --git a/src/warehouse/index.js b/src/warehouse/index.js index 3305a52762..b3d1c5e4bc 100644 --- a/src/warehouse/index.js +++ b/src/warehouse/index.js @@ -23,7 +23,7 @@ const whPageColumnMappingRules = require('./config/WHPageConfig.js'); const whScreenColumnMappingRules = require('./config/WHScreenConfig.js'); const whGroupColumnMappingRules = require('./config/WHGroupConfig.js'); const whAliasColumnMappingRules = require('./config/WHAliasConfig.js'); -const {isDataLakeProvider, isBlank} = require('./config/helpers'); +const { isDataLakeProvider, isBlank } = require('./config/helpers'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); const whExtractEventTableColumnMappingRules = require('./config/WHExtractEventTableConfig.js'); diff --git a/src/warehouse/util.js b/src/warehouse/util.js index 11d72bfbfd..b4b22721fd 100644 --- a/src/warehouse/util.js +++ b/src/warehouse/util.js @@ -4,7 +4,7 @@ const get = require('get-value'); const v0 = require('./v0/util'); const v1 = require('./v1/util'); const { PlatformError, InstrumentationError } = require('@rudderstack/integrations-lib'); -const {isBlank} = require('./config/helpers'); +const { isBlank } = require('./config/helpers'); const minTimeInMs = Date.parse('0001-01-01T00:00:00Z'); const maxTimeInMs = Date.parse('9999-12-31T23:59:59.999Z'); diff --git a/test/__mocks__/data/sources/shopify/response.json b/test/__mocks__/data/sources/shopify/response.json index ead25067e6..4eef747b94 100644 --- a/test/__mocks__/data/sources/shopify/response.json +++ b/test/__mocks__/data/sources/shopify/response.json @@ -31,4 +31,4 @@ "shopify_test_set_redis_error": { "itemsHash": "EMPTY" } -} \ No newline at end of file +} diff --git a/test/__tests__/data/customerio_source_input.json b/test/__tests__/data/customerio_source_input.json index 5b825d6a00..769c1b7fd3 100644 --- a/test/__tests__/data/customerio_source_input.json +++ b/test/__tests__/data/customerio_source_input.json @@ -111,9 +111,7 @@ "customer_id": "user-123", "delivery_id": "RAECAAFwnUSneIa0ZXkmq8EdkAM==", "headers": { - "Custom-Header": [ - "custom-value" - ] + "Custom-Header": ["custom-value"] }, "identifiers": { "id": "user-123" @@ -389,4 +387,4 @@ "metric": "delivered", "timestamp": 1585751830 } -] \ No newline at end of file +] diff --git a/test/__tests__/data/customerio_source_output.json b/test/__tests__/data/customerio_source_output.json index 24b964d01b..52df88e833 100644 --- a/test/__tests__/data/customerio_source_output.json +++ b/test/__tests__/data/customerio_source_output.json @@ -648,4 +648,4 @@ "originalTimestamp": "2020-04-01T14:37:10.000Z", "sentAt": "2020-04-01T14:37:10.000Z" } -] \ No newline at end of file +] diff --git a/test/__tests__/data/formsort_source.json b/test/__tests__/data/formsort_source.json index a12d84a98a..d94cfd677b 100644 --- a/test/__tests__/data/formsort_source.json +++ b/test/__tests__/data/formsort_source.json @@ -1,94 +1,94 @@ [ - { - "description": "when we receive finalized as false", - "input": { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": false, - "created_at": "2022-11-25T14:41:22+00:00" + { + "description": "when we receive finalized as false", + "input": { + "answers": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "flow_label": "new-flow-2022-11-25", + "variant_label": "main", + "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", + "finalized": false, + "created_at": "2022-11-25T14:41:22+00:00" + }, + "output": { + "context": { + "library": { + "name": "unknown", + "version": "unknown" + }, + "integration": { + "name": "Formsort" + }, + "page": { + "title": "new-flow-2022-11-25" }, - "output": { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowLoaded" - } + "variantLabel": "main", + "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" + }, + "integrations": { + "Formsort": false + }, + "type": "track", + "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "originalTimestamp": "2022-11-25T14:41:22+00:00", + "properties": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "event": "FlowLoaded" + } + }, + { + "description": "when we receive finalized as true", + "input": { + "answers": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "flow_label": "new-flow-2022-11-25", + "variant_label": "main", + "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", + "finalized": true, + "created_at": "2022-11-25T14:41:22+00:00" }, - { - "description": "when we receive finalized as true", - "input": { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": true, - "created_at": "2022-11-25T14:41:22+00:00" + "output": { + "context": { + "library": { + "name": "unknown", + "version": "unknown" + }, + "integration": { + "name": "Formsort" + }, + "page": { + "title": "new-flow-2022-11-25" }, - "output": { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowFinalized" - } + "variantLabel": "main", + "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" + }, + "integrations": { + "Formsort": false + }, + "type": "track", + "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "originalTimestamp": "2022-11-25T14:41:22+00:00", + "properties": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "event": "FlowFinalized" } -] \ No newline at end of file + } +] diff --git a/test/__tests__/data/proxy_input.json b/test/__tests__/data/proxy_input.json index a647238f9f..0d7ff24ab7 100644 --- a/test/__tests__/data/proxy_input.json +++ b/test/__tests__/data/proxy_input.json @@ -263,4 +263,4 @@ "destination": "any" } } -] \ No newline at end of file +] diff --git a/test/__tests__/data/shopify.json b/test/__tests__/data/shopify.json index 0153df4d26..48ca8c75a0 100644 --- a/test/__tests__/data/shopify.json +++ b/test/__tests__/data/shopify.json @@ -4,9 +4,7 @@ "input": { "id": "shopify_test3", "query_parameters": { - "topic": [ - "carts_create" - ] + "topic": ["carts_create"] }, "token": "shopify_test3", "line_items": [], @@ -33,12 +31,8 @@ "description": "Invalid topic", "input": { "query_parameters": { - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -50,12 +44,8 @@ "input": { "query_parameters": { "topic": [], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -66,15 +56,9 @@ "description": "Unsupported Event Type", "input": { "query_parameters": { - "topic": [ - "random_event" - ], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "topic": ["random_event"], + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -89,15 +73,9 @@ "description": "Identify Call for customers create event", "input": { "query_parameters": { - "topic": [ - "customers_create" - ], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "topic": ["customers_create"], + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] }, "id": 5747017285820, "email": "anuraj@rudderstack.com", @@ -256,15 +234,9 @@ "description": "Unsupported checkout event", "input": { "query_parameters": { - "topic": [ - "checkout_delete" - ], - "writeKey": [ - "sample-write-key" - ], - "signature": [ - "rudderstack" - ] + "topic": ["checkout_delete"], + "writeKey": ["sample-write-key"], + "signature": ["rudderstack"] }, "admin_graphql_api_id": "gid://shopify/Fulfillment/4124667937024", "created_at": "2022-01-05T18:13:02+05:30", @@ -292,13 +264,9 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", - "tracking_urls": [ - "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" - ], + "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-01-05T18:16:48+05:30" }, "output": { @@ -313,15 +281,9 @@ "description": "Track Call -> Fullfillments updated event", "input": { "query_parameters": { - "topic": [ - "fulfillments_update" - ], - "writeKey": [ - "sample-write-key" - ], - "signature": [ - "rudderstack" - ] + "topic": ["fulfillments_update"], + "writeKey": ["sample-write-key"], + "signature": ["rudderstack"] }, "shipping_address": { "address1": "11 Rani Sankari Lane Patuapara Bhowanipore" @@ -420,13 +382,9 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", - "tracking_urls": [ - "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" - ], + "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-01-05T18:16:48+05:30" }, "output": { @@ -462,9 +420,7 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", "tracking_urls": [ "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" @@ -556,4 +512,4 @@ } } } -] \ No newline at end of file +] diff --git a/test/integrations/destinations/adj/processor/data.ts b/test/integrations/destinations/adj/processor/data.ts index 2c208d0d08..e28a25cf59 100644 --- a/test/integrations/destinations/adj/processor/data.ts +++ b/test/integrations/destinations/adj/processor/data.ts @@ -2179,7 +2179,8 @@ export const data = [ status: 200, body: [ { - error: 'App Token is not present. Please configure your app token from config dashbaord', + error: + 'App Token is not present. Please configure your app token from config dashbaord', statTags: { destType: 'ADJ', errorCategory: 'dataValidation', @@ -2205,24 +2206,24 @@ export const data = [ body: [ { message: { - "type": "track", - "event": "Application Installed", - "sentAt": "2022-09-28T20:14:44.995Z", - "userId": "sample_user_id", - "context": { - "device": { - "id": "sample_device_id", - "type": "android", - "advertisingId": "_sample" + type: 'track', + event: 'Application Installed', + sentAt: '2022-09-28T20:14:44.995Z', + userId: 'sample_user_id', + context: { + device: { + id: 'sample_device_id', + type: 'android', + advertisingId: '_sample', + }, + traits: { + userId: '_sample_uid', + anonymousId: '_sample_anonid', }, - "traits": { - "userId": "_sample_uid", - "anonymousId": "_sample_anonid" - } - }, - "timestamp": "2022-09-28T20:14:43.314Z", - "request_ip": "71.189.106.156", - "originalTimestamp": "2022-09-28T20:14:44.995Z" + }, + timestamp: '2022-09-28T20:14:43.314Z', + request_ip: '71.189.106.156', + originalTimestamp: '2022-09-28T20:14:44.995Z', }, destination: { ID: '1i3Em7GMU9xVEiDlZUN8c88BMS9', @@ -2245,8 +2246,7 @@ export const data = [ }, Config: { appToken: 'testAppToken', - customMappings: [ - { from: 'Application Installed', to: '3fdmll' }], + customMappings: [{ from: 'Application Installed', to: '3fdmll' }], partnerParamsKeys: [ { from: 'key1', to: 'partnerParamKey-1' }, { from: 'key2', to: 'partnerParamKey-2' }, @@ -2277,10 +2277,10 @@ export const data = [ endpoint: 'https://s2s.adjust.com/event', headers: { Accept: '*/*' }, params: { - event_token: "3fdmll", - ip_address: "71.189.106.156", + event_token: '3fdmll', + ip_address: '71.189.106.156', android_id: 'sample_device_id', - gps_adid: "_sample", + gps_adid: '_sample', s2s: 1, app_token: 'testAppToken', environment: 'production', @@ -2294,4 +2294,5 @@ export const data = [ ], }, }, - },]; + }, +]; diff --git a/test/integrations/destinations/clevertap/network.ts b/test/integrations/destinations/clevertap/network.ts index c4eb23ee39..57a647e684 100644 --- a/test/integrations/destinations/clevertap/network.ts +++ b/test/integrations/destinations/clevertap/network.ts @@ -65,7 +65,8 @@ const dataDeliveryMocksData = [ method: 'POST', }, httpRes: { - data: { status: 'fail', error: 'Invalid Credentials', code: 401 }, status: 401 + data: { status: 'fail', error: 'Invalid Credentials', code: 401 }, + status: 401, }, }, { diff --git a/test/integrations/destinations/clickup/network.ts b/test/integrations/destinations/clickup/network.ts index 1a26209923..2cb7cde34f 100644 --- a/test/integrations/destinations/clickup/network.ts +++ b/test/integrations/destinations/clickup/network.ts @@ -1,247 +1,247 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.clickup.com/api/v2/list/correctListId123/field', - method: 'GET', - }, - httpRes: { - data: { - "fields": [ - { - "id": "19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e", - "name": "Labels", - "type": "labels", - "type_config": { - "options": [ - { - "id": "32c81c1c-cf53-4829-92f5-0f0270d27a45", - "label": "Option 1", - "color": {} - }, - { - "id": "7e24f329-9dd9-4e68-b426-2c70af6f9347", - "label": "Option 2", - "color": {} - } - ] - }, - "date_created": "1661964865880", - "hide_from_guests": false, - "required": false - }, - { - "id": "22eaffee-ffec-4c3b-bdae-56e69d55eecd", - "name": "Payment Status", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "new_drop_down": true, - "options": [ - { - "id": "e109e36b-a052-4a31-af16-25da7324990f", - "name": "Sent Request", - "color": "#FF7FAB", - "orderindex": 0 - }, - { - "id": "3a3b4512-2896-44f7-8075-2ff37777fe24", - "name": "Quote sent", - "color": "#EA80FC", - "orderindex": 1 - }, - { - "id": "7afcb6fb-cec8-41d8-bf0c-039a9db28460", - "name": "Pending", - "color": "#ff7800", - "orderindex": 2 - }, - { - "id": "890ecf28-bdd4-4f53-92cc-bc4edb696fcd", - "name": "Payment Recieved", - "color": "#2ecd6f", - "orderindex": 3 - }, - { - "id": "e89f7dd7-fd24-4b32-ac4d-f174d8ca914f", - "name": "n/a", - "color": "#b5bcc2", - "orderindex": 4 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "4b7a29be-e261-4340-8f3f-e6de838473e5", - "name": "Plan", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "new_drop_down": true, - "options": [ - { - "id": "4b9366a7-2592-4b7a-909a-ed4af705e27c", - "name": "Unlimited", - "color": "#02BCD4", - "orderindex": 0 - }, - { - "id": "c5032049-8c05-44e9-a000-3a071d457b8f", - "name": "Business", - "color": "#1bbc9c", - "orderindex": 1 - }, - { - "id": "9fb08801-1130-4650-8e2e-28578344ff3c", - "name": "Enterprise", - "color": "#2ecd6f", - "orderindex": 2 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "4bfebc00-9d4a-40d1-aef8-5a87b610186c", - "name": "Contact Title", - "type": "text", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "666f74bf-6d87-41f3-8735-ccf0efe066dd", - "name": "Date", - "type": "date", - "type_config": {}, - "date_created": "1662379321069", - "hide_from_guests": false, - "required": false - }, - { - "id": "a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c", - "name": "Industry", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "options": [ - { - "id": "75173398-257f-42b6-8bae-4cf767fa99ab", - "name": "Engineering", - "color": "#04A9F4", - "orderindex": 0 - }, - { - "id": "c7f9b6f5-cd98-4609-af10-68a8710cc1bf", - "name": "Retail", - "color": "#ff7800", - "orderindex": 1 - }, - { - "id": "dbe84940-b4e8-4a29-8491-e1aa5f2be4e2", - "name": "Hospitality", - "color": "#2ecd6f", - "orderindex": 2 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "b01b32fd-94d3-43e6-9f31-2c855ff169cd", - "name": "Url", - "type": "url", - "type_config": {}, - "date_created": "1661970432587", - "hide_from_guests": false, - "required": false - }, - { - "id": "c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6", - "name": "Phone Number", - "type": "phone", - "type_config": {}, - "date_created": "1661970795061", - "hide_from_guests": false, - "required": false - }, - { - "id": "d0201829-ddcd-4b97-b71f-0f9e672488f2", - "name": "Account Size", - "type": "number", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "ea6c1e48-2abf-4328-b228-79c213e147c8", - "name": "Location", - "type": "location", - "type_config": {}, - "date_created": "1662229589329", - "hide_from_guests": false, - "required": false - }, - { - "id": "ebe825fb-92de-41ce-a29c-25018da039b4", - "name": "Email", - "type": "email", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "f431cda3-a575-4a05-ba8d-583d9b6cb2df", - "name": "Rating", - "type": "emoji", - "type_config": { - "count": 5, - "code_point": "2b50" - }, - "date_created": "1661963909454", - "hide_from_guests": false, - "required": false - }, - { - "id": "ffbe4f03-cbc3-4077-8fea-9e5d08b4dceb", - "name": "Money In INR", - "type": "currency", - "type_config": { - "default": {}, - "precision": 2, - "currency_type": "INR" - }, - "date_created": "1661428276019", - "hide_from_guests": false, - "required": false - } - ] - }, - status: 200 - }, + { + httpReq: { + url: 'https://api.clickup.com/api/v2/list/correctListId123/field', + method: 'GET', }, - { - httpReq: { - url: 'https://api.clickup.com/api/v2/list/correctListId456/field', - method: 'GET', - }, - httpRes: { - data: { - "fields": [] + httpRes: { + data: { + fields: [ + { + id: '19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e', + name: 'Labels', + type: 'labels', + type_config: { + options: [ + { + id: '32c81c1c-cf53-4829-92f5-0f0270d27a45', + label: 'Option 1', + color: {}, + }, + { + id: '7e24f329-9dd9-4e68-b426-2c70af6f9347', + label: 'Option 2', + color: {}, + }, + ], + }, + date_created: '1661964865880', + hide_from_guests: false, + required: false, + }, + { + id: '22eaffee-ffec-4c3b-bdae-56e69d55eecd', + name: 'Payment Status', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + new_drop_down: true, + options: [ + { + id: 'e109e36b-a052-4a31-af16-25da7324990f', + name: 'Sent Request', + color: '#FF7FAB', + orderindex: 0, + }, + { + id: '3a3b4512-2896-44f7-8075-2ff37777fe24', + name: 'Quote sent', + color: '#EA80FC', + orderindex: 1, + }, + { + id: '7afcb6fb-cec8-41d8-bf0c-039a9db28460', + name: 'Pending', + color: '#ff7800', + orderindex: 2, + }, + { + id: '890ecf28-bdd4-4f53-92cc-bc4edb696fcd', + name: 'Payment Recieved', + color: '#2ecd6f', + orderindex: 3, + }, + { + id: 'e89f7dd7-fd24-4b32-ac4d-f174d8ca914f', + name: 'n/a', + color: '#b5bcc2', + orderindex: 4, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '4b7a29be-e261-4340-8f3f-e6de838473e5', + name: 'Plan', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + new_drop_down: true, + options: [ + { + id: '4b9366a7-2592-4b7a-909a-ed4af705e27c', + name: 'Unlimited', + color: '#02BCD4', + orderindex: 0, + }, + { + id: 'c5032049-8c05-44e9-a000-3a071d457b8f', + name: 'Business', + color: '#1bbc9c', + orderindex: 1, + }, + { + id: '9fb08801-1130-4650-8e2e-28578344ff3c', + name: 'Enterprise', + color: '#2ecd6f', + orderindex: 2, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '4bfebc00-9d4a-40d1-aef8-5a87b610186c', + name: 'Contact Title', + type: 'text', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '666f74bf-6d87-41f3-8735-ccf0efe066dd', + name: 'Date', + type: 'date', + type_config: {}, + date_created: '1662379321069', + hide_from_guests: false, + required: false, + }, + { + id: 'a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c', + name: 'Industry', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + options: [ + { + id: '75173398-257f-42b6-8bae-4cf767fa99ab', + name: 'Engineering', + color: '#04A9F4', + orderindex: 0, + }, + { + id: 'c7f9b6f5-cd98-4609-af10-68a8710cc1bf', + name: 'Retail', + color: '#ff7800', + orderindex: 1, + }, + { + id: 'dbe84940-b4e8-4a29-8491-e1aa5f2be4e2', + name: 'Hospitality', + color: '#2ecd6f', + orderindex: 2, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'b01b32fd-94d3-43e6-9f31-2c855ff169cd', + name: 'Url', + type: 'url', + type_config: {}, + date_created: '1661970432587', + hide_from_guests: false, + required: false, + }, + { + id: 'c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6', + name: 'Phone Number', + type: 'phone', + type_config: {}, + date_created: '1661970795061', + hide_from_guests: false, + required: false, + }, + { + id: 'd0201829-ddcd-4b97-b71f-0f9e672488f2', + name: 'Account Size', + type: 'number', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'ea6c1e48-2abf-4328-b228-79c213e147c8', + name: 'Location', + type: 'location', + type_config: {}, + date_created: '1662229589329', + hide_from_guests: false, + required: false, + }, + { + id: 'ebe825fb-92de-41ce-a29c-25018da039b4', + name: 'Email', + type: 'email', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'f431cda3-a575-4a05-ba8d-583d9b6cb2df', + name: 'Rating', + type: 'emoji', + type_config: { + count: 5, + code_point: '2b50', }, - status: 200 - }, - } + date_created: '1661963909454', + hide_from_guests: false, + required: false, + }, + { + id: 'ffbe4f03-cbc3-4077-8fea-9e5d08b4dceb', + name: 'Money In INR', + type: 'currency', + type_config: { + default: {}, + precision: 2, + currency_type: 'INR', + }, + date_created: '1661428276019', + hide_from_guests: false, + required: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.clickup.com/api/v2/list/correctListId456/field', + method: 'GET', + }, + httpRes: { + data: { + fields: [], + }, + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/custify/deleteUsers/data.ts b/test/integrations/destinations/custify/deleteUsers/data.ts index 3c5a461f69..22a120770a 100644 --- a/test/integrations/destinations/custify/deleteUsers/data.ts +++ b/test/integrations/destinations/custify/deleteUsers/data.ts @@ -129,8 +129,7 @@ export const data = [ }, { - description: - 'Test 4: should fail when one of the userAttributes does not contain `userId`', + description: 'Test 4: should fail when one of the userAttributes does not contain `userId`', input: { request: { body: [ @@ -140,8 +139,7 @@ export const data = [ { userId: 'rudder1', }, - { - }, + {}, ], config: { apiKey: 'dummyApiKey', diff --git a/test/integrations/destinations/delighted/network.ts b/test/integrations/destinations/delighted/network.ts index 15b0a414e6..d9896a25e8 100644 --- a/test/integrations/destinations/delighted/network.ts +++ b/test/integrations/destinations/delighted/network.ts @@ -1,30 +1,30 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.delighted.com/v1/people.json', - method: 'GET', - headers: { Authorization: `Basic ZHVtbXlBcGlLZXk=` }, - params: { - email: "identified_user@email.com" - } - }, - httpRes: { - data: ["user data"], - status: 200 - }, + { + httpReq: { + url: 'https://api.delighted.com/v1/people.json', + method: 'GET', + headers: { Authorization: `Basic ZHVtbXlBcGlLZXk=` }, + params: { + email: 'identified_user@email.com', + }, }, - { - httpReq: { - url: 'https://api.delighted.com/v1/people.json', - method: 'GET', - headers: { Authorization: `Basic ZHVtbXlBcGlLZXlmb3JmYWlsdXJl` }, - params: { - email: "unidentified_user@email.com" - } - }, - httpRes: { - data: [], - status: 200 - }, - } + httpRes: { + data: ['user data'], + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.delighted.com/v1/people.json', + method: 'GET', + headers: { Authorization: `Basic ZHVtbXlBcGlLZXlmb3JmYWlsdXJl` }, + params: { + email: 'unidentified_user@email.com', + }, + }, + httpRes: { + data: [], + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/fb_custom_audience/network.ts b/test/integrations/destinations/fb_custom_audience/network.ts index fa11f28370..9b498bc07e 100644 --- a/test/integrations/destinations/fb_custom_audience/network.ts +++ b/test/integrations/destinations/fb_custom_audience/network.ts @@ -512,14 +512,15 @@ export const networkCallsData = [ httpRes: { data: { error: { - message: 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + message: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', type: 'OAuthException', code: 190, error_subcode: 463, - fbtrace_id: 'A3b8C6PpI-kdIOwPwV4PANi' + fbtrace_id: 'A3b8C6PpI-kdIOwPwV4PANi', }, }, status: 400, }, - } + }, ]; diff --git a/test/integrations/destinations/freshmarketer/network.ts b/test/integrations/destinations/freshmarketer/network.ts index 51f1a0c115..9d661f2686 100644 --- a/test/integrations/destinations/freshmarketer/network.ts +++ b/test/integrations/destinations/freshmarketer/network.ts @@ -1,487 +1,495 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "sales_account": { - "id": 70003771396, - "name": "postman2.0", - "address": "Red Colony", - "city": "Pune", - "state": "Goa", - "zipcode": null, - "country": null, - "number_of_employees": 11, - "annual_revenue": 1000, - "website": null, - "owner_id": null, - "phone": "919191919191", - "open_deals_amount": null, - "open_deals_count": null, - "won_deals_amount": null, - "won_deals_count": null, - "last_contacted": null, - "last_contacted_mode": null, - "facebook": null, - "twitter": null, - "linkedin": null, - "links": { - "conversations": "/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "document_associations": "/crm/sales/sales_accounts/70003771396/document_associations", - "notes": "/crm/sales/sales_accounts/70003771396/notes?include=creater", - "tasks": "/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note" - }, - "custom_field": {}, - "created_at": "2022-08-17T04:15:00-04:00", - "updated_at": "2022-08-24T06:03:31-04:00", - "avatar": null, - "parent_sales_account_id": null, - "recent_note": null, - "last_contacted_via_sales_activity": null, - "last_contacted_sales_activity_mode": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "last_assigned_at": null, - "tags": [], - "is_deleted": false, - "team_user_ids": null, - "has_connections": false, - "record_type_id": "71010794477" - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70042006456, - "first_name": "Rk", - "last_name": "Mishra", - "display_name": "Rk Mishra", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "testuser@google.com", - "emails": [ - { - "id": 70037311213, - "value": "testuser@google.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": "IST", - "work_number": "9988776655", - "mobile_number": "19265559504", - "address": null, - "last_seen": null, - "lead_score": 26, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70042006456/timeline_feeds", - "document_associations": "/crm/sales/contacts/70042006456/document_associations", - "notes": "/crm/sales/contacts/70042006456/notes?include=creater", - "tasks": "/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70042006456/duplicates", - "connections": "/crm/sales/contacts/70042006456/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-08-09T03:22:12-04:00", - "updated_at": "2022-08-30T00:33:27-04:00", - "keyword": "drilling", - "medium": "facebook", - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": "2022-08-29T05:51:24-04:00", - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": "ea5cfab2-3961-4d8a-8187-3d1858c99099", - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 2, - "record_type_id": "71010794476", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [], - "sales_accounts": [ - { - "partial": true, - "id": 70003771198, - "name": "div-quer", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": true - }, - { - "partial": true, - "id": 70003825177, - "name": "BisleriGroup", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": false - } - ] - } - }, - status: 200 + httpRes: { + data: { + sales_account: { + id: 70003771396, + name: 'postman2.0', + address: 'Red Colony', + city: 'Pune', + state: 'Goa', + zipcode: null, + country: null, + number_of_employees: 11, + annual_revenue: 1000, + website: null, + owner_id: null, + phone: '919191919191', + open_deals_amount: null, + open_deals_count: null, + won_deals_amount: null, + won_deals_count: null, + last_contacted: null, + last_contacted_mode: null, + facebook: null, + twitter: null, + linkedin: null, + links: { + conversations: + '/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + document_associations: '/crm/sales/sales_accounts/70003771396/document_associations', + notes: '/crm/sales/sales_accounts/70003771396/notes?include=creater', + tasks: + '/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + }, + custom_field: {}, + created_at: '2022-08-17T04:15:00-04:00', + updated_at: '2022-08-24T06:03:31-04:00', + avatar: null, + parent_sales_account_id: null, + recent_note: null, + last_contacted_via_sales_activity: null, + last_contacted_sales_activity_mode: null, + completed_sales_sequences: null, + active_sales_sequences: null, + last_assigned_at: null, + tags: [], + is_deleted: false, + team_user_ids: null, + has_connections: false, + record_type_id: '71010794477', }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', - method: 'GET' - }, - httpRes: { - data: { - "sales_activity_types": [ - { - "partial": true, - "id": 70000666879, - "name": "own-calender", - "internal_name": "cappointment", - "show_in_conversation": true, - "position": 1, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663932, - "name": "fb-support", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 2, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663746, - "name": "twitter sales", - "internal_name": "twitter", - "show_in_conversation": true, - "position": 3, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000646396, - "name": "linked sales", - "internal_name": "linkedin", - "show_in_conversation": true, - "position": 4, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000642330, - "name": "facebook sales", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 5, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612897, - "name": "Chat", - "internal_name": "chat", - "show_in_conversation": true, - "position": 6, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612898, - "name": "Phone", - "internal_name": "phone", - "show_in_conversation": true, - "position": 7, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612899, - "name": "Meeting", - "internal_name": "appointment", - "show_in_conversation": true, - "position": 8, - "is_default": true, - "is_checkedin": true - }, - { - "partial": true, - "id": 70000612900, - "name": "Task", - "internal_name": "task", - "show_in_conversation": true, - "position": 9, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612901, - "name": "Email", - "internal_name": "email", - "show_in_conversation": true, - "position": 10, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612902, - "name": "SMS Outgoing", - "internal_name": "sms_outgoing", - "show_in_conversation": true, - "position": 11, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612903, - "name": "Reminder", - "internal_name": "reminder", - "show_in_conversation": false, - "position": 12, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612904, - "name": "SMS Incoming", - "internal_name": "sms_incoming", - "show_in_conversation": true, - "position": 13, - "is_default": true, - "is_checkedin": false - } - ] - }, - status: 200 - }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70054866612, - "first_name": null, - "last_name": null, - "display_name": "jamessampleton120@gmail.com", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "jamessampleton120@gmail.com", - "emails": [ - { - "id": 70047409219, - "value": "jamessampleton120@gmail.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": null, - "work_number": null, - "mobile_number": null, - "address": null, - "last_seen": null, - "lead_score": 0, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70054866612/timeline_feeds", - "document_associations": "/crm/sales/contacts/70054866612/document_associations", - "notes": "/crm/sales/contacts/70054866612/notes?include=creater", - "tasks": "/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70054866612/duplicates", - "connections": "/crm/sales/contacts/70054866612/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-10-11T08:42:15-04:00", - "updated_at": "2022-10-11T08:42:15-04:00", - "keyword": null, - "medium": null, - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": null, - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": null, - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 0, - "record_type_id": "71012139284", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [] - } + httpRes: { + data: { + contact: { + id: 70042006456, + first_name: 'Rk', + last_name: 'Mishra', + display_name: 'Rk Mishra', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'testuser@google.com', + emails: [ + { + id: 70037311213, + value: 'testuser@google.com', + is_primary: true, + label: null, + _destroy: false, + }, + ], + time_zone: 'IST', + work_number: '9988776655', + mobile_number: '19265559504', + address: null, + last_seen: null, + lead_score: 26, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70042006456/timeline_feeds', + document_associations: '/crm/sales/contacts/70042006456/document_associations', + notes: '/crm/sales/contacts/70042006456/notes?include=creater', + tasks: + '/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70042006456/duplicates', + connections: '/crm/sales/contacts/70042006456/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-08-09T03:22:12-04:00', + updated_at: '2022-08-30T00:33:27-04:00', + keyword: 'drilling', + medium: 'facebook', + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: '2022-08-29T05:51:24-04:00', + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 2, + record_type_id: '71010794476', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], + sales_accounts: [ + { + partial: true, + id: 70003771198, + name: 'div-quer', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: true, + }, + { + partial: true, + id: 70003825177, + name: 'BisleriGroup', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: false, }, - status: 200 + ], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', - method: 'GET' - }, - httpRes: { - data: { - "lists": [ - { - "id": 70000053624, - "name": "Sample list" - }, - { - "id": 70000056575, - "name": "list1-test" - }, - { - "id": 70000058627, - "name": "Jio 5G Group" - }, - { - "id": 70000058628, - "name": "Airtel 5G Group" - }, - { - "id": 70000059716, - "name": "Voda 5G" - } - ], - "meta": { - "total_pages": 1, - "total": 5 - } + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', + method: 'GET', + }, + httpRes: { + data: { + sales_activity_types: [ + { + partial: true, + id: 70000666879, + name: 'own-calender', + internal_name: 'cappointment', + show_in_conversation: true, + position: 1, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663932, + name: 'fb-support', + internal_name: 'facebook', + show_in_conversation: true, + position: 2, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663746, + name: 'twitter sales', + internal_name: 'twitter', + show_in_conversation: true, + position: 3, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000646396, + name: 'linked sales', + internal_name: 'linkedin', + show_in_conversation: true, + position: 4, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000642330, + name: 'facebook sales', + internal_name: 'facebook', + show_in_conversation: true, + position: 5, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000612897, + name: 'Chat', + internal_name: 'chat', + show_in_conversation: true, + position: 6, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612898, + name: 'Phone', + internal_name: 'phone', + show_in_conversation: true, + position: 7, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612899, + name: 'Meeting', + internal_name: 'appointment', + show_in_conversation: true, + position: 8, + is_default: true, + is_checkedin: true, + }, + { + partial: true, + id: 70000612900, + name: 'Task', + internal_name: 'task', + show_in_conversation: true, + position: 9, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612901, + name: 'Email', + internal_name: 'email', + show_in_conversation: true, + position: 10, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612902, + name: 'SMS Outgoing', + internal_name: 'sms_outgoing', + show_in_conversation: true, + position: 11, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612903, + name: 'Reminder', + internal_name: 'reminder', + show_in_conversation: false, + position: 12, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612904, + name: 'SMS Incoming', + internal_name: 'sms_incoming', + show_in_conversation: true, + position: 13, + is_default: true, + is_checkedin: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', + method: 'POST', + }, + httpRes: { + data: { + contact: { + id: 70054866612, + first_name: null, + last_name: null, + display_name: 'jamessampleton120@gmail.com', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'jamessampleton120@gmail.com', + emails: [ + { + id: 70047409219, + value: 'jamessampleton120@gmail.com', + is_primary: true, + label: null, + _destroy: false, }, - status: 200 + ], + time_zone: null, + work_number: null, + mobile_number: null, + address: null, + last_seen: null, + lead_score: 0, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70054866612/timeline_feeds', + document_associations: '/crm/sales/contacts/70054866612/document_associations', + notes: '/crm/sales/contacts/70054866612/notes?include=creater', + tasks: + '/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70054866612/duplicates', + connections: '/crm/sales/contacts/70054866612/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-10-11T08:42:15-04:00', + updated_at: '2022-10-11T08:42:15-04:00', + keyword: null, + medium: null, + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: null, + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: null, + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 0, + record_type_id: '71012139284', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', - method: 'GET' - }, - httpRes: { - data: { - "lifecycle_stages": [ - { - "id": 71012139274, - "name": "Sales Qualified Lead start", - "position": 1, - "disabled": false, - "default": true, - "type": "Sales Qualified Lead", - "contact_status_ids": [70000697858, 70000697859, 70000697860] - }, - { - "id": 71012139273, - "name": "Lead", - "position": 2, - "disabled": false, - "default": true, - "type": "Lead", - "contact_status_ids": [70000697854, 70000697855, 70000697856, 70000697857] - }, - { - "id": 71012806409, - "name": "final Customer", - "position": 3, - "disabled": false, - "default": false, - "type": "Custom", - "contact_status_ids": [70000736543, 70000736544] - }, - { - "id": 71012139275, - "name": "Customer", - "position": 4, - "disabled": false, - "default": true, - "type": "Customer", - "contact_status_ids": [70000697861, 70000697862] - } - ] - }, - status: 200 + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', + method: 'GET', + }, + httpRes: { + data: { + lists: [ + { + id: 70000053624, + name: 'Sample list', + }, + { + id: 70000056575, + name: 'list1-test', + }, + { + id: 70000058627, + name: 'Jio 5G Group', + }, + { + id: 70000058628, + name: 'Airtel 5G Group', + }, + { + id: 70000059716, + name: 'Voda 5G', + }, + ], + meta: { + total_pages: 1, + total: 5, }, - } + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', + method: 'GET', + }, + httpRes: { + data: { + lifecycle_stages: [ + { + id: 71012139274, + name: 'Sales Qualified Lead start', + position: 1, + disabled: false, + default: true, + type: 'Sales Qualified Lead', + contact_status_ids: [70000697858, 70000697859, 70000697860], + }, + { + id: 71012139273, + name: 'Lead', + position: 2, + disabled: false, + default: true, + type: 'Lead', + contact_status_ids: [70000697854, 70000697855, 70000697856, 70000697857], + }, + { + id: 71012806409, + name: 'final Customer', + position: 3, + disabled: false, + default: false, + type: 'Custom', + contact_status_ids: [70000736543, 70000736544], + }, + { + id: 71012139275, + name: 'Customer', + position: 4, + disabled: false, + default: true, + type: 'Customer', + contact_status_ids: [70000697861, 70000697862], + }, + ], + }, + status: 200, + }, + }, ]; - - - diff --git a/test/integrations/destinations/freshsales/network.ts b/test/integrations/destinations/freshsales/network.ts index f6043b265f..9d661f2686 100644 --- a/test/integrations/destinations/freshsales/network.ts +++ b/test/integrations/destinations/freshsales/network.ts @@ -1,484 +1,495 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "sales_account": { - "id": 70003771396, - "name": "postman2.0", - "address": "Red Colony", - "city": "Pune", - "state": "Goa", - "zipcode": null, - "country": null, - "number_of_employees": 11, - "annual_revenue": 1000, - "website": null, - "owner_id": null, - "phone": "919191919191", - "open_deals_amount": null, - "open_deals_count": null, - "won_deals_amount": null, - "won_deals_count": null, - "last_contacted": null, - "last_contacted_mode": null, - "facebook": null, - "twitter": null, - "linkedin": null, - "links": { - "conversations": "/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "document_associations": "/crm/sales/sales_accounts/70003771396/document_associations", - "notes": "/crm/sales/sales_accounts/70003771396/notes?include=creater", - "tasks": "/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note" - }, - "custom_field": {}, - "created_at": "2022-08-17T04:15:00-04:00", - "updated_at": "2022-08-24T06:03:31-04:00", - "avatar": null, - "parent_sales_account_id": null, - "recent_note": null, - "last_contacted_via_sales_activity": null, - "last_contacted_sales_activity_mode": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "last_assigned_at": null, - "tags": [], - "is_deleted": false, - "team_user_ids": null, - "has_connections": false, - "record_type_id": "71010794477" - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70042006456, - "first_name": "Rk", - "last_name": "Mishra", - "display_name": "Rk Mishra", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "testuser@google.com", - "emails": [ - { - "id": 70037311213, - "value": "testuser@google.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": "IST", - "work_number": "9988776655", - "mobile_number": "19265559504", - "address": null, - "last_seen": null, - "lead_score": 26, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70042006456/timeline_feeds", - "document_associations": "/crm/sales/contacts/70042006456/document_associations", - "notes": "/crm/sales/contacts/70042006456/notes?include=creater", - "tasks": "/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70042006456/duplicates", - "connections": "/crm/sales/contacts/70042006456/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-08-09T03:22:12-04:00", - "updated_at": "2022-08-30T00:33:27-04:00", - "keyword": "drilling", - "medium": "facebook", - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": "2022-08-29T05:51:24-04:00", - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": "ea5cfab2-3961-4d8a-8187-3d1858c99099", - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 2, - "record_type_id": "71010794476", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [], - "sales_accounts": [ - { - "partial": true, - "id": 70003771198, - "name": "div-quer", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": true - }, - { - "partial": true, - "id": 70003825177, - "name": "BisleriGroup", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": false - } - ] - } - }, - status: 200 + httpRes: { + data: { + sales_account: { + id: 70003771396, + name: 'postman2.0', + address: 'Red Colony', + city: 'Pune', + state: 'Goa', + zipcode: null, + country: null, + number_of_employees: 11, + annual_revenue: 1000, + website: null, + owner_id: null, + phone: '919191919191', + open_deals_amount: null, + open_deals_count: null, + won_deals_amount: null, + won_deals_count: null, + last_contacted: null, + last_contacted_mode: null, + facebook: null, + twitter: null, + linkedin: null, + links: { + conversations: + '/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + document_associations: '/crm/sales/sales_accounts/70003771396/document_associations', + notes: '/crm/sales/sales_accounts/70003771396/notes?include=creater', + tasks: + '/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + }, + custom_field: {}, + created_at: '2022-08-17T04:15:00-04:00', + updated_at: '2022-08-24T06:03:31-04:00', + avatar: null, + parent_sales_account_id: null, + recent_note: null, + last_contacted_via_sales_activity: null, + last_contacted_sales_activity_mode: null, + completed_sales_sequences: null, + active_sales_sequences: null, + last_assigned_at: null, + tags: [], + is_deleted: false, + team_user_ids: null, + has_connections: false, + record_type_id: '71010794477', }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', - method: 'GET' - }, - httpRes: { - data: { - "sales_activity_types": [ - { - "partial": true, - "id": 70000666879, - "name": "own-calender", - "internal_name": "cappointment", - "show_in_conversation": true, - "position": 1, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663932, - "name": "fb-support", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 2, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663746, - "name": "twitter sales", - "internal_name": "twitter", - "show_in_conversation": true, - "position": 3, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000646396, - "name": "linked sales", - "internal_name": "linkedin", - "show_in_conversation": true, - "position": 4, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000642330, - "name": "facebook sales", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 5, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612897, - "name": "Chat", - "internal_name": "chat", - "show_in_conversation": true, - "position": 6, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612898, - "name": "Phone", - "internal_name": "phone", - "show_in_conversation": true, - "position": 7, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612899, - "name": "Meeting", - "internal_name": "appointment", - "show_in_conversation": true, - "position": 8, - "is_default": true, - "is_checkedin": true - }, - { - "partial": true, - "id": 70000612900, - "name": "Task", - "internal_name": "task", - "show_in_conversation": true, - "position": 9, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612901, - "name": "Email", - "internal_name": "email", - "show_in_conversation": true, - "position": 10, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612902, - "name": "SMS Outgoing", - "internal_name": "sms_outgoing", - "show_in_conversation": true, - "position": 11, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612903, - "name": "Reminder", - "internal_name": "reminder", - "show_in_conversation": false, - "position": 12, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612904, - "name": "SMS Incoming", - "internal_name": "sms_incoming", - "show_in_conversation": true, - "position": 13, - "is_default": true, - "is_checkedin": false - } - ] - }, - status: 200 - }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70054866612, - "first_name": null, - "last_name": null, - "display_name": "jamessampleton120@gmail.com", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "jamessampleton120@gmail.com", - "emails": [ - { - "id": 70047409219, - "value": "jamessampleton120@gmail.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": null, - "work_number": null, - "mobile_number": null, - "address": null, - "last_seen": null, - "lead_score": 0, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70054866612/timeline_feeds", - "document_associations": "/crm/sales/contacts/70054866612/document_associations", - "notes": "/crm/sales/contacts/70054866612/notes?include=creater", - "tasks": "/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70054866612/duplicates", - "connections": "/crm/sales/contacts/70054866612/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-10-11T08:42:15-04:00", - "updated_at": "2022-10-11T08:42:15-04:00", - "keyword": null, - "medium": null, - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": null, - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": null, - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 0, - "record_type_id": "71012139284", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [] - } + httpRes: { + data: { + contact: { + id: 70042006456, + first_name: 'Rk', + last_name: 'Mishra', + display_name: 'Rk Mishra', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'testuser@google.com', + emails: [ + { + id: 70037311213, + value: 'testuser@google.com', + is_primary: true, + label: null, + _destroy: false, + }, + ], + time_zone: 'IST', + work_number: '9988776655', + mobile_number: '19265559504', + address: null, + last_seen: null, + lead_score: 26, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70042006456/timeline_feeds', + document_associations: '/crm/sales/contacts/70042006456/document_associations', + notes: '/crm/sales/contacts/70042006456/notes?include=creater', + tasks: + '/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70042006456/duplicates', + connections: '/crm/sales/contacts/70042006456/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-08-09T03:22:12-04:00', + updated_at: '2022-08-30T00:33:27-04:00', + keyword: 'drilling', + medium: 'facebook', + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: '2022-08-29T05:51:24-04:00', + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 2, + record_type_id: '71010794476', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], + sales_accounts: [ + { + partial: true, + id: 70003771198, + name: 'div-quer', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: true, + }, + { + partial: true, + id: 70003825177, + name: 'BisleriGroup', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: false, }, - status: 200 + ], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', - method: 'GET' - }, - httpRes: { - data: { - "lists": [ - { - "id": 70000053624, - "name": "Sample list" - }, - { - "id": 70000056575, - "name": "list1-test" - }, - { - "id": 70000058627, - "name": "Jio 5G Group" - }, - { - "id": 70000058628, - "name": "Airtel 5G Group" - }, - { - "id": 70000059716, - "name": "Voda 5G" - } - ], - "meta": { - "total_pages": 1, - "total": 5 - } + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', + method: 'GET', + }, + httpRes: { + data: { + sales_activity_types: [ + { + partial: true, + id: 70000666879, + name: 'own-calender', + internal_name: 'cappointment', + show_in_conversation: true, + position: 1, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663932, + name: 'fb-support', + internal_name: 'facebook', + show_in_conversation: true, + position: 2, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663746, + name: 'twitter sales', + internal_name: 'twitter', + show_in_conversation: true, + position: 3, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000646396, + name: 'linked sales', + internal_name: 'linkedin', + show_in_conversation: true, + position: 4, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000642330, + name: 'facebook sales', + internal_name: 'facebook', + show_in_conversation: true, + position: 5, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000612897, + name: 'Chat', + internal_name: 'chat', + show_in_conversation: true, + position: 6, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612898, + name: 'Phone', + internal_name: 'phone', + show_in_conversation: true, + position: 7, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612899, + name: 'Meeting', + internal_name: 'appointment', + show_in_conversation: true, + position: 8, + is_default: true, + is_checkedin: true, + }, + { + partial: true, + id: 70000612900, + name: 'Task', + internal_name: 'task', + show_in_conversation: true, + position: 9, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612901, + name: 'Email', + internal_name: 'email', + show_in_conversation: true, + position: 10, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612902, + name: 'SMS Outgoing', + internal_name: 'sms_outgoing', + show_in_conversation: true, + position: 11, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612903, + name: 'Reminder', + internal_name: 'reminder', + show_in_conversation: false, + position: 12, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612904, + name: 'SMS Incoming', + internal_name: 'sms_incoming', + show_in_conversation: true, + position: 13, + is_default: true, + is_checkedin: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', + method: 'POST', + }, + httpRes: { + data: { + contact: { + id: 70054866612, + first_name: null, + last_name: null, + display_name: 'jamessampleton120@gmail.com', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'jamessampleton120@gmail.com', + emails: [ + { + id: 70047409219, + value: 'jamessampleton120@gmail.com', + is_primary: true, + label: null, + _destroy: false, }, - status: 200 + ], + time_zone: null, + work_number: null, + mobile_number: null, + address: null, + last_seen: null, + lead_score: 0, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70054866612/timeline_feeds', + document_associations: '/crm/sales/contacts/70054866612/document_associations', + notes: '/crm/sales/contacts/70054866612/notes?include=creater', + tasks: + '/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70054866612/duplicates', + connections: '/crm/sales/contacts/70054866612/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-10-11T08:42:15-04:00', + updated_at: '2022-10-11T08:42:15-04:00', + keyword: null, + medium: null, + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: null, + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: null, + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 0, + record_type_id: '71012139284', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', - method: 'GET' - }, - httpRes: { - data: { - "lifecycle_stages": [ - { - "id": 71012139274, - "name": "Sales Qualified Lead start", - "position": 1, - "disabled": false, - "default": true, - "type": "Sales Qualified Lead", - "contact_status_ids": [70000697858, 70000697859, 70000697860] - }, - { - "id": 71012139273, - "name": "Lead", - "position": 2, - "disabled": false, - "default": true, - "type": "Lead", - "contact_status_ids": [70000697854, 70000697855, 70000697856, 70000697857] - }, - { - "id": 71012806409, - "name": "final Customer", - "position": 3, - "disabled": false, - "default": false, - "type": "Custom", - "contact_status_ids": [70000736543, 70000736544] - }, - { - "id": 71012139275, - "name": "Customer", - "position": 4, - "disabled": false, - "default": true, - "type": "Customer", - "contact_status_ids": [70000697861, 70000697862] - } - ] - }, - status: 200 + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', + method: 'GET', + }, + httpRes: { + data: { + lists: [ + { + id: 70000053624, + name: 'Sample list', + }, + { + id: 70000056575, + name: 'list1-test', + }, + { + id: 70000058627, + name: 'Jio 5G Group', + }, + { + id: 70000058628, + name: 'Airtel 5G Group', + }, + { + id: 70000059716, + name: 'Voda 5G', + }, + ], + meta: { + total_pages: 1, + total: 5, }, - } -]; \ No newline at end of file + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', + method: 'GET', + }, + httpRes: { + data: { + lifecycle_stages: [ + { + id: 71012139274, + name: 'Sales Qualified Lead start', + position: 1, + disabled: false, + default: true, + type: 'Sales Qualified Lead', + contact_status_ids: [70000697858, 70000697859, 70000697860], + }, + { + id: 71012139273, + name: 'Lead', + position: 2, + disabled: false, + default: true, + type: 'Lead', + contact_status_ids: [70000697854, 70000697855, 70000697856, 70000697857], + }, + { + id: 71012806409, + name: 'final Customer', + position: 3, + disabled: false, + default: false, + type: 'Custom', + contact_status_ids: [70000736543, 70000736544], + }, + { + id: 71012139275, + name: 'Customer', + position: 4, + disabled: false, + default: true, + type: 'Customer', + contact_status_ids: [70000697861, 70000697862], + }, + ], + }, + status: 200, + }, + }, +]; diff --git a/test/integrations/destinations/gainsight/network.ts b/test/integrations/destinations/gainsight/network.ts index c8adf871b9..4c5a026847 100644 --- a/test/integrations/destinations/gainsight/network.ts +++ b/test/integrations/destinations/gainsight/network.ts @@ -1,71 +1,71 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/query/Company', - method: 'POST', - }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "47d9c8be-4912-4610-806c-0eec22b73236", - "data": { - "records": [] - }, - "message": null - }, - status: 200 - }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/query/Company', + method: 'POST', }, - { - httpReq: { - url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company', - method: 'POST', + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '47d9c8be-4912-4610-806c-0eec22b73236', + data: { + records: [], }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "3ce46d4a-6a83-4a92-97b3-d9788a296af8", - "data": { - "count": 1, - "errors": null, - "records": [ - { - "Gsid": "1P0203VCESP7AUQMV9E953G" - } - ] - }, - "message": null + message: null, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company', + method: 'POST', + }, + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '3ce46d4a-6a83-4a92-97b3-d9788a296af8', + data: { + count: 1, + errors: null, + records: [ + { + Gsid: '1P0203VCESP7AUQMV9E953G', }, - status: 200 + ], }, + message: null, + }, + status: 200, }, - { - httpReq: { - url: "https://demo-domain.gainsightcloud.com/v1/data/objects/Company?keys=Name", - method: 'GET', - }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "30630809-40a7-45d2-9673-ac2e80d06f33", - "data": { - "count": 1, - "errors": null, - "records": [ - { - "Gsid": "1P0203VCESP7AUQMV9E953G" - } - ] - }, - "message": null + }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company?keys=Name', + method: 'GET', + }, + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '30630809-40a7-45d2-9673-ac2e80d06f33', + data: { + count: 1, + errors: null, + records: [ + { + Gsid: '1P0203VCESP7AUQMV9E953G', }, - status: 200 + ], }, - } + message: null, + }, + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/gainsight_px/network.ts b/test/integrations/destinations/gainsight_px/network.ts index d9dd6bbaa0..81a2da4bed 100644 --- a/test/integrations/destinations/gainsight_px/network.ts +++ b/test/integrations/destinations/gainsight_px/network.ts @@ -1,222 +1,222 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/sample-user-id', - method: 'GET', - }, - httpRes: { - data: { - "aptrinsicId": "347c4c87-98c7-4ca6-a6da-678ed6924c22", - "identifyId": "sample-user-id", - "type": "USER", - "gender": "MALE", - "email": "user@email.com", - "firstName": "Sample", - "lastName": "User", - "lastSeenDate": 0, - "signUpDate": 1624431528295, - "firstVisitDate": 0, - "title": "engineer", - "phone": "", - "score": 0, - "role": "", - "subscriptionId": "", - "accountId": "", - "numberOfVisits": 1, - "location": { - "countryName": "USA", - "countryCode": "US", - "stateName": "", - "stateCode": "", - "city": "New York", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624431528295, - "lastModifiedDate": 1624431528295, - "customAttributes": null, - "globalUnsubscribe": false, - "sfdcContactId": "", - "lastVisitedUserAgentData": null, - "id": "sample-user-id", - "lastInferredLocation": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/sample-user-id', + method: 'GET', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', - method: 'GET', + httpRes: { + data: { + aptrinsicId: '347c4c87-98c7-4ca6-a6da-678ed6924c22', + identifyId: 'sample-user-id', + type: 'USER', + gender: 'MALE', + email: 'user@email.com', + firstName: 'Sample', + lastName: 'User', + lastSeenDate: 0, + signUpDate: 1624431528295, + firstVisitDate: 0, + title: 'engineer', + phone: '', + score: 0, + role: '', + subscriptionId: '', + accountId: '', + numberOfVisits: 1, + location: { + countryName: 'USA', + countryCode: 'US', + stateName: '', + stateCode: '', + city: 'New York', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 200 + propertyKeys: ['AP-XABC-123'], + createDate: 1624431528295, + lastModifiedDate: 1624431528295, + customAttributes: null, + globalUnsubscribe: false, + sfdcContactId: '', + lastVisitedUserAgentData: null, + id: 'sample-user-id', + lastInferredLocation: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, + }, + status: 200, }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', - method: 'PUT', - }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 204 - }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', + method: 'GET', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/absent-id', - method: 'GET', + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - httpRes: { - data: { - externalapierror: { - status: "NOT_FOUND", - message: "User was not found for parameters {id=absent-id}", - debugMessage: null, - subErrors: null - } - }, - status: 404 + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', + method: 'PUT', + }, + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 204, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/absent-id', + method: 'GET', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/stanley-kubrick', - method: 'GET', + httpRes: { + data: { + externalapierror: { + status: 'NOT_FOUND', + message: 'User was not found for parameters {id=absent-id}', + debugMessage: null, + subErrors: null, }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 200 + }, + status: 404, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/stanley-kubrick', + method: 'GET', + }, + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - } + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/iterable/deleteUsers/data.ts b/test/integrations/destinations/iterable/deleteUsers/data.ts new file mode 100644 index 0000000000..79d801f4ee --- /dev/null +++ b/test/integrations/destinations/iterable/deleteUsers/data.ts @@ -0,0 +1,186 @@ +const destType = 'iterable'; + +export const data = [ + { + name: destType, + description: 'Test 0: should fail when config is not being sent', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + ], + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'Config for deletion not present', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 1: should fail when apiKey is not present in config', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder2', + }, + ], + config: { + apiToken: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'api key for deletion not present', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 2: should fail when one of the user-deletion requests fails', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder2', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: + 'User deletion request failed for userIds : [{"userId":"rudder2","Reason":"User does not exist. Email: UserId: rudder2"}]', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 3: should fail when invalid api key is set in config', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder3', + }, + { + userId: 'rudder4', + }, + ], + config: { + apiKey: 'invalidKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 401, + body: [ + { + error: 'User deletion request failed : Invalid API key', + statusCode: 401, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 4: should pass when proper apiKey & valid users are sent to destination', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder5', + }, + { + userId: 'rudder6', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 200, + status: 'successful', + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/iterable/network.ts b/test/integrations/destinations/iterable/network.ts new file mode 100644 index 0000000000..39544b2647 --- /dev/null +++ b/test/integrations/destinations/iterable/network.ts @@ -0,0 +1,109 @@ +const deleteNwData = [ + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder1', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder1 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder2', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'User does not exist. Email: UserId: rudder2', + code: 'BadParams', + params: null, + }, + status: 400, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder3', + headers: { + api_key: 'invalidKey', + }, + }, + httpRes: { + data: { + msg: 'Invalid API key', + code: 'Success', + params: { + endpoint: '/api/users/byUserId/rudder3', + }, + }, + status: 401, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder4', + headers: { + api_key: 'invalidKey', + }, + }, + httpRes: { + data: { + msg: 'Invalid API key', + code: 'Success', + params: { + endpoint: '/api/users/byUserId/rudder4', + }, + }, + status: 401, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder5', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder6 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder6', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder6 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, +]; +export const networkCallsData = [...deleteNwData]; diff --git a/test/integrations/destinations/klaviyo/network.ts b/test/integrations/destinations/klaviyo/network.ts index aa788a60da..d76d235c6f 100644 --- a/test/integrations/destinations/klaviyo/network.ts +++ b/test/integrations/destinations/klaviyo/network.ts @@ -1,75 +1,73 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://a.klaviyo.com/api/v2/list/XUepkK/subscribe', - method: 'GET', - }, - httpRes: { - status: 200 - }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/v2/list/XUepkK/subscribe', + method: 'GET', }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/v2/list/XUepkK/members', - method: 'GET', - }, - httpRes: { - status: 200 - }, + httpRes: { + status: 200, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'GET', - data: { - attributes: { - email: "test3@rudderstack.com" - } - } - }, - httpRes: { - status: 409, - data: { - } - }, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/v2/list/XUepkK/members', + method: 'GET', }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'GET', - }, - httpRes: { - status: 201, - data: { - data: { - id: '01GW3PHVY0MTCDGS0A1612HARX', - attributes: {} - }, - } - }, + httpRes: { + status: 200, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'POST', - headers: { Authorization: 'Klaviyo-API-Key dummyPrivateApiKeyforfailure' } - }, - httpRes: { + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'GET', + data: { + attributes: { + email: 'test3@rudderstack.com', }, + }, + }, + httpRes: { + status: 409, + data: {}, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'POST', + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'GET', + }, + httpRes: { + status: 201, + data: { + data: { + id: '01GW3PHVY0MTCDGS0A1612HARX', + attributes: {}, }, - httpRes: { - status: 201, - data: { - data: { - id: '01GW3PHVY0MTCDGS0A1612HARX', - attributes: {} - }, - } + }, + }, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'POST', + headers: { Authorization: 'Klaviyo-API-Key dummyPrivateApiKeyforfailure' }, + }, + httpRes: {}, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'POST', + }, + httpRes: { + status: 201, + data: { + data: { + id: '01GW3PHVY0MTCDGS0A1612HARX', + attributes: {}, }, - } + }, + }, + }, ]; diff --git a/test/integrations/destinations/wootric/network.ts b/test/integrations/destinations/wootric/network.ts index 2407efa62b..1b51cc700c 100644 --- a/test/integrations/destinations/wootric/network.ts +++ b/test/integrations/destinations/wootric/network.ts @@ -1,183 +1,182 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/dummyId1?lookup_by_external_id=true', - method: 'GET', - }, - httpRes: { - status: 200, - data: { - "id": 486438462, - "created_at": "2022-08-10 11:39:50 -0700", - "updated_at": "2022-08-10 11:39:50 -0700", - "email": "dummyuser1@gmail.com", - "last_surveyed": "2022-01-20 05:39:21 -0800", - "external_created_at": 1611149961, - "last_seen_at": null, - "properties": { - "city": "Mumbai", - "name": "Dummy User 1", - "title": "SDE", - "gender": "Male", - "company": "Rudderstack" - }, - "phone_number": "+19123456789", - "external_id": "dummyId1", - "last_response": null, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/dummyId1?lookup_by_external_id=true', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/exclueFunTestId?lookup_by_external_id=true', - method: 'GET', - }, - httpRes: { - status: 200, - data: { - "id": 486336190, - "created_at": "2022-08-10 07:30:50 -0700", - "updated_at": "2022-08-10 10:12:46 -0700", - "email": "excludeUser@gmail.com", - "last_surveyed": "2022-01-20 05:39:21 -0800", - "external_created_at": 1579755367, - "last_seen_at": null, - "properties": { - "city": "Mumbai", - "name": "exclude test user", - "email": "excludeUser@gmail.com", - "title": "AD", - "gender": "Male", - "company": "Rockstar" - }, - "phone_number": "+18324671283", - "external_id": "exclueFunTestId", - "last_response": null, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - }, + httpRes: { + status: 200, + data: { + id: 486438462, + created_at: '2022-08-10 11:39:50 -0700', + updated_at: '2022-08-10 11:39:50 -0700', + email: 'dummyuser1@gmail.com', + last_surveyed: '2022-01-20 05:39:21 -0800', + external_created_at: 1611149961, + last_seen_at: null, + properties: { + city: 'Mumbai', + name: 'Dummy User 1', + title: 'SDE', + gender: 'Male', + company: 'Rudderstack', + }, + phone_number: '+19123456789', + external_id: 'dummyId1', + last_response: null, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/my-external-id-1234?lookup_by_external_id=true', - method: 'POST', - - }, - httpRes: { - status: 200, - data: { - "type": "error_list", - "errors": [ - { - "status": "record_not_found", - "message": "The record could not be found", - "field": null - } - ] - } - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/exclueFunTestId?lookup_by_external_id=true', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/490635419', - method: 'GET' - }, - httpRes: { - data: { - "id": 490635419, - "created_at": "2022-08-20 00:55:26 -0700", - "updated_at": "2022-08-22 11:17:05 -0700", - "email": "firstuser@gmail.com", - "last_surveyed": "2022-08-01 00:11:44 -0700", - "external_created_at": 1661002761, - "last_seen_at": null, - "properties": { - "Department": "Marketing", - "product_plan": "Web", - "revenue amount": "5000" - }, - "phone_number": "+8859133456781", - "external_id": "firstUserId123", - "last_response": { - "id": 101013218, - "score": 9, - "text": "Good !!!", - "survey": { - "channel": "web" - } - }, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - status: 200, - }, + httpRes: { + status: 200, + data: { + id: 486336190, + created_at: '2022-08-10 07:30:50 -0700', + updated_at: '2022-08-10 10:12:46 -0700', + email: 'excludeUser@gmail.com', + last_surveyed: '2022-01-20 05:39:21 -0800', + external_created_at: 1579755367, + last_seen_at: null, + properties: { + city: 'Mumbai', + name: 'exclude test user', + email: 'excludeUser@gmail.com', + title: 'AD', + gender: 'Male', + company: 'Rockstar', + }, + phone_number: '+18324671283', + external_id: 'exclueFunTestId', + last_response: null, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, }, - { - httpReq: { - url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken', - method: 'POST' - }, - httpRes: { - data: { - "access_token": "2fe581c1c72851e73d60f4191f720be93e5d3e8a6147e37c4e8e852b1a8f506c", - "token_type": "Bearer", - "expires_in": 7200, - "refresh_token": "f4033a61742e84405a5ef8b2e09b82395dc041f0259fd5fb715fc196a1b9cd52", - "scope": "delete_account admin respond export read survey invalidate_response", - "created_at": 1660292389 - }, - status: 200, - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/my-external-id-1234?lookup_by_external_id=true', + method: 'POST', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/dummyId2?lookup_by_external_id=true', - method: 'GET' - }, - httpRes: { - status: 200, - }, + httpRes: { + status: 200, + data: { + type: 'error_list', + errors: [ + { + status: 'record_not_found', + message: 'The record could not be found', + field: null, + }, + ], + }, }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/12345', - method: 'GET' - }, - httpRes: { - status: 200, - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/490635419', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken12', - method: 'POST' - }, - httpRes: { - data: { - error: "Not found", - status: 404 - } - }, - } + httpRes: { + data: { + id: 490635419, + created_at: '2022-08-20 00:55:26 -0700', + updated_at: '2022-08-22 11:17:05 -0700', + email: 'firstuser@gmail.com', + last_surveyed: '2022-08-01 00:11:44 -0700', + external_created_at: 1661002761, + last_seen_at: null, + properties: { + Department: 'Marketing', + product_plan: 'Web', + 'revenue amount': '5000', + }, + phone_number: '+8859133456781', + external_id: 'firstUserId123', + last_response: { + id: 101013218, + score: 9, + text: 'Good !!!', + survey: { + channel: 'web', + }, + }, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken', + method: 'POST', + }, + httpRes: { + data: { + access_token: '2fe581c1c72851e73d60f4191f720be93e5d3e8a6147e37c4e8e852b1a8f506c', + token_type: 'Bearer', + expires_in: 7200, + refresh_token: 'f4033a61742e84405a5ef8b2e09b82395dc041f0259fd5fb715fc196a1b9cd52', + scope: 'delete_account admin respond export read survey invalidate_response', + created_at: 1660292389, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/dummyId2?lookup_by_external_id=true', + method: 'GET', + }, + httpRes: { + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/12345', + method: 'GET', + }, + httpRes: { + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken12', + method: 'POST', + }, + httpRes: { + data: { + error: 'Not found', + status: 404, + }, + }, + }, ]; diff --git a/tsconfig.json b/tsconfig.json index 9db40dd0e1..926831b612 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ /* Language and Environment */ "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "lib": [ - "es2019" + "es2019", ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ @@ -100,8 +100,8 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, }, "exclude": ["./src/**/*.test.js", "./src/**/*.test.ts", "./test"], - "include": ["./src", "./src/**/*.json"] + "include": ["./src", "./src/**/*.json"], } From 2ebff956ff2aa74b008a8de832a31d8774d2d47e Mon Sep 17 00:00:00 2001 From: Anant Jain <62471433+anantjain45823@users.noreply.github.com> Date: Tue, 27 Feb 2024 08:50:00 +0530 Subject: [PATCH 33/33] fix: rakuten: sync property mapping sourcekeys to rudderstack standard spec (#3129) * fix: rakuten: sync property mapping sourcekeys to rudderstack standard spec * chore: comments addressed --- .../rakuten/data/propertiesMapping.json | 76 ++++++++++++++----- .../processor/transformationFailure.ts | 6 +- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json index e04765faed..db5d36fc4d 100644 --- a/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json +++ b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json @@ -1,25 +1,34 @@ [ { - "sourceKeys": "properties.orderId", + "sourceKeys": ["properties.order_id", "properties.orderId"], "required": true, "destKey": "ord" }, { - "sourceKeys": ["properties.tr", "properties.ranSiteID"], + "sourceKeys": ["properties.tr", "properties.ran_site_id", "properties.ranSiteID"], "required": true, "destKey": "tr" }, { - "sourceKeys": ["properties.land", "properties.landTime"], + "sourceKeys": ["properties.land", "properties.land_time", "properties.landTime"], "required": true, "destKey": "land" }, { - "sourceKeys": ["properties.date", "properties.orderCompletedTime"], + "sourceKeys": [ + "properties.date", + "properties.order_completed_time", + "properties.orderCompletedTime" + ], "destKey": "date" }, { - "sourceKeys": ["properties.altord", "properties.alterOrderId"], + "sourceKeys": [ + "properties.alt_ord", + "properties.altord", + "properties.alter_order_id", + "properties.alterOrderId" + ], "destKey": "altord" }, { @@ -27,15 +36,15 @@ "destKey": "cur" }, { - "sourceKeys": "properties.creditCardType", + "sourceKeys": ["properties.credit_card_type", "properties.creditCardType"], "destKey": "cc" }, { - "sourceKeys": "properties.commReason", + "sourceKeys": ["properties.comm_reason", "properties.commReason"], "destKey": "commreason" }, { - "sourceKeys": "properties.isComm", + "sourceKeys": ["properties.is_comm", "properties.isComm"], "destKey": "iscomm" }, { @@ -47,27 +56,48 @@ "destKey": "coupon" }, { - "sourceKeys": ["properties.custId", "properties.customerId", "properties.userId"], + "sourceKeys": [ + "properties.cust_id", + "properties.custId", + "properties.customer_id", + "properties.customerId", + "properties.userId" + ], "destKey": "custid" }, { - "sourceKeys": ["properties.custScore", "properties.customerScore"], + "sourceKeys": [ + "properties.cust_score", + "properties.custScore", + "properties.customer_score", + "properties.customerScore" + ], "destKey": "custscore" }, { - "sourceKeys": ["properties.custStatus", "properties.customerStatus"], + "sourceKeys": [ + "properties.cust_status", + "properties.custStatus", + "properties.customer_status", + "properties.customerStatus" + ], "destKey": "custstatus" }, { - "sourceKeys": ["properties.dId", "properties.advertisingId"], + "sourceKeys": ["properties.dId", "properties.advertising_id", "properties.advertisingId"], "destKey": "did" }, { - "sourceKeys": ["properties.disamt", "properties.discountAmout"], + "sourceKeys": ["properties.disamt", "properties.discount_amount", "properties.discountAmount"], "destKey": "disamt" }, { - "sourceKeys": ["properties.ordStatus", "properties.orderStatus"], + "sourceKeys": [ + "properties.ord_status", + "properties.ordStatus", + "properties.order_status", + "properties.orderStatus" + ], "destKey": "ordstatus" }, { @@ -75,7 +105,7 @@ "destKey": "segment" }, { - "sourceKeys": "properties.shipcountry", + "sourceKeys": ["properties.ship_country", "properties.shipcountry"], "destKey": "shipcountry" }, { @@ -83,15 +113,25 @@ "destKey": "shipped" }, { - "sourceKeys": ["properties.sitename", "properties.url", "context.page.url"], + "sourceKeys": [ + "properties.site_name", + "properties.sitename", + "properties.url", + "context.page.url" + ], "destKey": "sitename" }, { - "sourceKeys": "properties.storeId", + "sourceKeys": ["properties.store_id", "properties.storeId"], "destKey": "storeid" }, { - "sourceKeys": ["properties.storecat", "properties.storeCategory"], + "sourceKeys": [ + "properties.store_cat", + "properties.storecat", + "properties.store_category", + "properties.storeCategory" + ], "destKey": "storecat" }, { diff --git a/test/integrations/destinations/rakuten/processor/transformationFailure.ts b/test/integrations/destinations/rakuten/processor/transformationFailure.ts index 906ddafd6a..e35ab26b69 100644 --- a/test/integrations/destinations/rakuten/processor/transformationFailure.ts +++ b/test/integrations/destinations/rakuten/processor/transformationFailure.ts @@ -46,7 +46,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from "properties.orderId": Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from "properties.orderId"', + 'Missing required value from ["properties.order_id","properties.orderId"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.order_id","properties.orderId"]', metadata: { destinationId: 'dummyDestId', jobId: '1', @@ -245,7 +245,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from ["properties.tr","properties.ranSiteID"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.tr","properties.ranSiteID"]', + 'Missing required value from ["properties.tr","properties.ran_site_id","properties.ranSiteID"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.tr","properties.ran_site_id","properties.ranSiteID"]', metadata: { destinationId: 'dummyDestId', jobId: '1', @@ -312,7 +312,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from ["properties.land","properties.landTime"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.land","properties.landTime"]', + 'Missing required value from ["properties.land","properties.land_time","properties.landTime"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.land","properties.land_time","properties.landTime"]', metadata: { destinationId: 'dummyDestId', jobId: '1',