From 4be29973b92f9cf22b3d9cc0503b0f900a4232df Mon Sep 17 00:00:00 2001 From: gitcommitshow <56937085+gitcommitshow@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:46:41 +0530 Subject: [PATCH 1/5] chore: remove outdated config generator info from contributor guide (#3139) chore: remove config generator info from contribution guide --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1cef57af73..bdd76d916c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,6 @@ See the project's [README](README.md) for further information about working in t - Include instructions on how to test your changes. 3. Your branch may be merged once all configured checks pass, including: - A review from appropriate maintainers -4. Along with the PR in transformer raise a PR against [config-generator][config-generator] with the configurations. ## Committing From 75e9f462b0ff9b9a8abab3c78dc7d147926e9e5e Mon Sep 17 00:00:00 2001 From: AASHISH MALIK Date: Tue, 27 Feb 2024 19:32:38 +0530 Subject: [PATCH 2/5] fix: convert to string from null in hs (#3136) --- src/v0/destinations/hs/util.js | 5 +- src/v0/util/index.js | 2 + .../destinations/hs/processor/data.ts | 104 ++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/v0/destinations/hs/util.js b/src/v0/destinations/hs/util.js index 32ee923f5f..359c93dc1a 100644 --- a/src/v0/destinations/hs/util.js +++ b/src/v0/destinations/hs/util.js @@ -19,6 +19,7 @@ const { getHashFromArray, getDestinationExternalIDInfoForRetl, getValueFromMessage, + isNull, } = require('../../util'); const { CONTACT_PROPERTY_MAP_ENDPOINT, @@ -223,7 +224,9 @@ const getTransformedJSON = async (message, destination, propertyMap) => { // lowercase and replace ' ' & '.' with '_' const hsSupportedKey = formatKey(traitsKey); if (!rawPayload[traitsKey] && propertyMap[hsSupportedKey]) { - let propValue = traits[traitsKey]; + // HS accepts empty string to remove the property from contact + // https://community.hubspot.com/t5/APIs-Integrations/Clearing-values-of-custom-properties-in-Hubspot-contact-using/m-p/409156 + let propValue = isNull(traits[traitsKey]) ? '' : traits[traitsKey]; if (propertyMap[hsSupportedKey] === 'date') { propValue = getUTCMidnightTimeStampValue(propValue); } diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 1d952693f2..9792401241 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -52,6 +52,7 @@ const removeUndefinedAndNullAndEmptyValues = (obj) => lodash.pickBy(obj, isDefinedAndNotNullAndNotEmpty); const isBlank = (value) => lodash.isEmpty(lodash.toString(value)); const flattenMap = (collection) => lodash.flatMap(collection, (x) => x); +const isNull = (x) => lodash.isNull(x); // ======================================================================== // GENERIC UTLITY // ======================================================================== @@ -2266,6 +2267,7 @@ module.exports = { isDefinedAndNotNullAndNotEmpty, isEmpty, isNotEmpty, + isNull, isEmptyObject, isHttpStatusRetryable, isHttpStatusSuccess, diff --git a/test/integrations/destinations/hs/processor/data.ts b/test/integrations/destinations/hs/processor/data.ts index 03ad9d0a3b..f45f3a719b 100644 --- a/test/integrations/destinations/hs/processor/data.ts +++ b/test/integrations/destinations/hs/processor/data.ts @@ -1,3 +1,45 @@ +import { Destination } from '../../../../../src/types'; +import { generateMetadata, generateSimplifiedIdentifyPayload } from '../../../testUtils'; + +const commonOutputHeaders = { + 'Content-Type': 'application/json', + Authorization: 'Bearer dummy-access-token', +}; + +const destination: Destination = { + Config: { + authorizationType: 'newPrivateAppApi', + accessToken: 'dummy-access-token', + hubID: 'dummy-hubId', + apiKey: 'dummy-apikey', + apiVersion: 'newApi', + lookupField: 'email', + hubspotEvents: [], + eventFilteringOption: 'disable', + blacklistedEvents: [ + { + eventName: '', + }, + ], + whitelistedEvents: [ + { + eventName: '', + }, + ], + }, + Enabled: true, + ID: '123', + Name: 'hs', + DestinationDefinition: { + ID: '123', + Name: 'hs', + DisplayName: 'Hubspot', + Config: {}, + }, + WorkspaceID: '123', + Transformations: [], +}; + export const data = [ { name: 'hs', @@ -5269,4 +5311,66 @@ export const data = [ }, }, }, + { + name: 'hs', + description: 'Test coversion of null to string values', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: generateSimplifiedIdentifyPayload({ + userId: '12345', + context: { + traits: { + email: 'noname@email.com', + firstname: null, + gender: '', + lookupField: 'email', + }, + }, + }), + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + userId: '', + method: 'POST', + endpoint: 'https://api.hubapi.com/crm/v3/objects/contacts', + files: {}, + headers: commonOutputHeaders, + operation: 'createContacts', + params: {}, + body: { + FORM: {}, + JSON: { + properties: { + email: 'noname@email.com', + firstname: '', + gender: '', + }, + }, + JSON_ARRAY: {}, + XML: {}, + }, + }, + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, ]; From 0486049ba2ad96b50d8f29e96b46b96a8a5c9f76 Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Wed, 28 Feb 2024 01:42:21 +0530 Subject: [PATCH 3/5] feat: add support for interaction events in sfmc (#3109) * feat: add support for interaction events in sfmc * chore: refactorx1 * chore: fix response structure * chore: add test * chore: map contect key from properties instead of userId * chore: add component test --- src/v0/destinations/sfmc/config.js | 1 + src/v0/destinations/sfmc/transform.js | 35 +++- src/v0/destinations/sfmc/transform.test.js | 42 ++++- .../destinations/sfmc/processor/data.ts | 162 ++++++++++++++++++ 4 files changed, 237 insertions(+), 3 deletions(-) diff --git a/src/v0/destinations/sfmc/config.js b/src/v0/destinations/sfmc/config.js index f856c44d6b..b275eca1ec 100644 --- a/src/v0/destinations/sfmc/config.js +++ b/src/v0/destinations/sfmc/config.js @@ -4,6 +4,7 @@ const ENDPOINTS = { GET_TOKEN: `auth.marketingcloudapis.com/v2/token`, CONTACTS: `rest.marketingcloudapis.com/contacts/v1/contacts`, INSERT_CONTACTS: `rest.marketingcloudapis.com/hub/v1/dataevents/key:`, + EVENT: "rest.marketingcloudapis.com/interaction/v1/events", }; const CONFIG_CATEGORIES = { diff --git a/src/v0/destinations/sfmc/transform.js b/src/v0/destinations/sfmc/transform.js index 553ceb2828..53925bc7ed 100644 --- a/src/v0/destinations/sfmc/transform.js +++ b/src/v0/destinations/sfmc/transform.js @@ -1,3 +1,4 @@ +/* eslint-disable no-param-reassign */ /* eslint-disable no-nested-ternary */ const { NetworkError, @@ -188,6 +189,26 @@ const responseBuilderForInsertData = ( return response; }; +// DOC : https://developer.salesforce.com/docs/marketing/marketing-cloud/references/mc_rest_interaction/postEvent.html + +const responseBuilderForMessageEvent = (message, subDomain, authToken, hashMapEventDefinition) => { + const contactKey = message.properties.contactId; + delete message.properties.contactId; + const response = defaultRequestConfig(); + response.method = defaultPostRequestConfig.requestMethod; + response.endpoint = `https://${subDomain}.${ENDPOINTS.EVENT}`; + response.headers = { + 'Content-Type': JSON_MIME_TYPE, + Authorization: `Bearer ${authToken}`, + }; + response.body.JSON = { + ContactKey: contactKey, + EventDefinitionKey: hashMapEventDefinition[message.event.toLowerCase()], + Data: { ...message.properties }, + }; + return response; +}; + const responseBuilderSimple = async (message, category, destination) => { const { clientId, @@ -198,6 +219,7 @@ const responseBuilderSimple = async (message, category, destination) => { eventToExternalKey, eventToPrimaryKey, eventToUUID, + eventToDefinitionMapping, } = destination.Config; // map from an event name to an external key of a data extension. const hashMapExternalKey = getHashFromArray(eventToExternalKey, 'from', 'to'); @@ -207,6 +229,8 @@ const responseBuilderSimple = async (message, category, destination) => { const hashMapUUID = getHashFromArray(eventToUUID, 'event', 'uuid'); // token needed for authorization for subsequent calls const authToken = await getToken(clientId, clientSecret, subDomain); + // map from an event name to an event definition key. + const hashMapEventDefinition = getHashFromArray(eventToDefinitionMapping, 'from', 'to'); // if createOrUpdateContacts is true identify calls for create and update of contacts will not occur. if (category.type === 'identify' && !createOrUpdateContacts) { // first call to identify the contact @@ -240,10 +264,12 @@ const responseBuilderSimple = async (message, category, destination) => { if (typeof message.event !== 'string') { throw new ConfigurationError('Event name must be a string'); } + if (hashMapEventDefinition[message.event.toLowerCase()]) { + return responseBuilderForMessageEvent(message, subDomain, authToken, hashMapEventDefinition); + } if (!isDefinedAndNotNull(hashMapExternalKey[message.event.toLowerCase()])) { throw new ConfigurationError('Event not mapped for this track call'); } - return responseBuilderForInsertData( message, hashMapExternalKey[message.event.toLowerCase()], @@ -293,4 +319,9 @@ const processRouterDest = async (inputs, reqMetadata) => { return respList; }; -module.exports = { process, processRouterDest, responseBuilderSimple }; +module.exports = { + process, + processRouterDest, + responseBuilderSimple, + responseBuilderForMessageEvent, +}; diff --git a/src/v0/destinations/sfmc/transform.test.js b/src/v0/destinations/sfmc/transform.test.js index c49c49017c..8d382ef649 100644 --- a/src/v0/destinations/sfmc/transform.test.js +++ b/src/v0/destinations/sfmc/transform.test.js @@ -1,7 +1,7 @@ const { ConfigurationError } = require('@rudderstack/integrations-lib'); const axios = require('axios'); const MockAxiosAdapter = require('axios-mock-adapter'); -const { responseBuilderSimple } = require('./transform'); +const { responseBuilderSimple, responseBuilderForMessageEvent } = require('./transform'); beforeAll(() => { const mock = new MockAxiosAdapter(axios); mock @@ -122,4 +122,44 @@ describe('responseBuilderSimple', () => { expect(response).toHaveProperty('body.JSON'); expect(response).toHaveProperty('headers'); }); + + it('should build response object with correct details for message event', () => { + const message = { + userId: 'u123', + event: 'testEvent', + properties: { + contactId: '12345', + prop1: 'value1', + prop2: 'value2', + }, + }; + const subDomain = 'subdomain'; + const authToken = 'token'; + const hashMapEventDefinition = { + testevent: 'eventDefinitionKey', + }; + + const response = responseBuilderForMessageEvent( + message, + subDomain, + authToken, + hashMapEventDefinition, + ); + expect(response.method).toBe('POST'); + expect(response.endpoint).toBe( + 'https://subdomain.rest.marketingcloudapis.com/interaction/v1/events', + ); + expect(response.headers).toEqual({ + 'Content-Type': 'application/json', + Authorization: 'Bearer token', + }); + expect(response.body.JSON).toEqual({ + ContactKey: '12345', + EventDefinitionKey: 'eventDefinitionKey', + Data: { + prop1: 'value1', + prop2: 'value2', + }, + }); + }); }); diff --git a/test/integrations/destinations/sfmc/processor/data.ts b/test/integrations/destinations/sfmc/processor/data.ts index 406ed82ace..b2839908ad 100644 --- a/test/integrations/destinations/sfmc/processor/data.ts +++ b/test/integrations/destinations/sfmc/processor/data.ts @@ -1732,4 +1732,166 @@ export const data = [ }, }, }, + { + name: 'sfmc', + description: 'Test 12', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + event: 'message event', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + campaign: { + name: 'Demo Campaign', + source: 'facebook', + medium: 'online', + term: 'Demo terms', + content: 'Demo content', + }, + traits: { + email: 'tonmoy@rudderstack.com', + name: 'Tonmoy Labs', + }, + 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', + screen: { + density: 2, + height: 860, + width: 1280, + }, + }, + type: 'track', + userId: '12345', + properties: { + id: 'id101', + contactId: 'cid101', + email: 'testemail@gmail.com', + accountNumber: '99110099', + patronName: 'SP', + }, + sentAt: '2019-10-14T09:03:22.563Z', + integrations: { + All: true, + }, + }, + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'SFMC', + DestinationDefinition: { + ID: '1pYpYSeQd8OeN6xPdw6VGDzqUd1', + Name: 'SFMC', + DisplayName: 'Salesforce Marketing Cloud', + Config: { + destConfig: [], + excludeKeys: [], + includeKeys: [], + saveDestinationResponse: false, + supportedSourceTypes: [], + transformAt: 'processor', + }, + ResponseRules: {}, + }, + Config: { + clientId: 'vcn7AQ2W9GGIAZSsN6Mfq', + clientSecret: 'vcn7AQ2W9GGIAZSsN6Mfq', + createOrUpdateContacts: false, + eventDelivery: true, + eventDeliveryTS: 1615371070621, + eventToExternalKey: [ + { + from: 'Event Name', + to: 'C500FD37-155C-49BD-A21B-AFCEF3D1A9CB', + }, + { + from: 'Watch', + to: 'C500FD37-155C-49BD-A21B-AFCEF3D1A9CB', + }, + ], + eventToPrimaryKey: [ + { + from: 'userId', + to: 'User Key', + }, + { + from: 'watch', + to: 'Guest Key, Contact Key', + }, + ], + eventToUUID: [ + { + event: 'Event Name', + uuid: true, + }, + ], + eventToDefinitionMapping: [ + { + from: 'message event', + to: 'test-event-definition', + }, + ], + externalKey: 'f3ffa19b-e0b3-4967-829f-549b781080e6', + subDomain: 'vcn7AQ2W9GGIAZSsN6Mfq', + }, + Enabled: true, + Transformations: [], + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + JSON: { + ContactKey: 'cid101', + Data: { + accountNumber: '99110099', + email: 'testemail@gmail.com', + id: 'id101', + patronName: 'SP', + }, + EventDefinitionKey: 'test-event-definition', + }, + }, + type: 'REST', + files: {}, + method: 'POST', + params: {}, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer yourAuthToken', + }, + version: '1', + endpoint: + 'https://vcn7AQ2W9GGIAZSsN6Mfq.rest.marketingcloudapis.com/interaction/v1/events', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; From 657b7805eb01da25a007d978198d5debf03917fd Mon Sep 17 00:00:00 2001 From: Sankeerth Date: Wed, 28 Feb 2024 10:09:22 +0530 Subject: [PATCH 4/5] fix: version deprecation failure false positive (#3104) --- src/v0/destinations/gainsight_px/util.js | 8 +- src/v0/destinations/sfmc/config.js | 2 +- .../destinations/gainsight_px/network.ts | 29 ++++ .../destinations/gainsight_px/router/data.ts | 153 ++++++++++++++++++ 4 files changed, 187 insertions(+), 5 deletions(-) diff --git a/src/v0/destinations/gainsight_px/util.js b/src/v0/destinations/gainsight_px/util.js index e03fbbf148..83d23566dd 100644 --- a/src/v0/destinations/gainsight_px/util.js +++ b/src/v0/destinations/gainsight_px/util.js @@ -6,13 +6,13 @@ const { getDynamicErrorType } = require('../../../adapters/utils/networkUtils'); const { JSON_MIME_TYPE } = require('../../util/constant'); const handleErrorResponse = (error, customErrMessage, expectedErrStatus, defaultStatus = 400) => { + let destResp; let errMessage = ''; let errorStatus = defaultStatus; if (error.response && error.response.data) { - errMessage = error.response.data.externalapierror - ? JSON.stringify(error.response.data.externalapierror) - : JSON.stringify(error.response.data); + destResp = error.response?.data?.externalapierror ?? error.response?.data; + errMessage = JSON.stringify(destResp); errorStatus = error.response.status; @@ -26,7 +26,7 @@ const handleErrorResponse = (error, customErrMessage, expectedErrStatus, default { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(errorStatus), }, - error, + destResp, ); }; diff --git a/src/v0/destinations/sfmc/config.js b/src/v0/destinations/sfmc/config.js index b275eca1ec..1b1f5c323b 100644 --- a/src/v0/destinations/sfmc/config.js +++ b/src/v0/destinations/sfmc/config.js @@ -4,7 +4,7 @@ const ENDPOINTS = { GET_TOKEN: `auth.marketingcloudapis.com/v2/token`, CONTACTS: `rest.marketingcloudapis.com/contacts/v1/contacts`, INSERT_CONTACTS: `rest.marketingcloudapis.com/hub/v1/dataevents/key:`, - EVENT: "rest.marketingcloudapis.com/interaction/v1/events", + EVENT: 'rest.marketingcloudapis.com/interaction/v1/events', }; const CONFIG_CATEGORIES = { diff --git a/test/integrations/destinations/gainsight_px/network.ts b/test/integrations/destinations/gainsight_px/network.ts index 81a2da4bed..99f51d9d8e 100644 --- a/test/integrations/destinations/gainsight_px/network.ts +++ b/test/integrations/destinations/gainsight_px/network.ts @@ -219,4 +219,33 @@ export const networkCallsData = [ status: 200, }, }, + // Axios Error + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/myUId', + headers: { 'X-APTRINSIC-API-KEY': 'sample-api-key', 'Content-Type': 'application/json' }, + method: 'GET', + }, + httpRes: { + message: 'Request failed with status code 403', + name: 'AxiosError', + stack: + 'AxiosError: Request failed with status code 403\n at settle (/Users/saisankeerth/rudderstack/rudder-transformer/node_modules/axios/lib/core/settle.js:19:12)\n at IncomingMessage.handleStreamEnd (/Users/saisankeerth/rudderstack/rudder-transformer/node_modules/axios/lib/adapters/http.js:589:11)\n at IncomingMessage.emit (node:events:529:35)\n at IncomingMessage.emit (node:domain:489:12)\n at endReadableNT (node:internal/streams/readable:1400:12)\n at processTicksAndRejections (node:internal/process/task_queues:82:21)', + config: { + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + 'X-APTRINSIC-API-KEY': 'sample-api-key', + 'User-Agent': 'axios/1.6.5', + 'Accept-Encoding': 'gzip, compress, deflate, br', + }, + method: 'get', + dummy: 'upgrade required', // keyword + url: 'https://api.aptrinsic.com/v1/users/myUId', + }, + code: 'FORBIDDEN', + status: 403, + data: '\u003c!doctype html\u003e\u003cmeta charset="utf-8"\u003e\u003cmeta name=viewport content="width=device-width, initial-scale=1"\u003e\u003ctitle\u003e403\u003c/title\u003e403 Forbidden', + }, + }, ]; diff --git a/test/integrations/destinations/gainsight_px/router/data.ts b/test/integrations/destinations/gainsight_px/router/data.ts index 7dc131127d..1b3d5be875 100644 --- a/test/integrations/destinations/gainsight_px/router/data.ts +++ b/test/integrations/destinations/gainsight_px/router/data.ts @@ -1,3 +1,75 @@ +const metadata = { + userId: '9a7820d0-0ff2-4451-b655-682cec15cbd2', + jobId: 1, + sourceId: '1s9eG8UCer6YSKsD8ZlQCyLa3pj', + destinationId: 'desId2', + attemptNum: 0, + receivedAt: '2021-06-25T14:29:52.911+05:30', + createdAt: '2021-06-25T08:59:56.329Z', + firstAttemptedAt: '', + transformAt: 'router', +}; +const destination2 = { + ID: 'desId2', + Name: 'gainsight-px-dest', + DestinationDefinition: { + ID: 'destDef1', + Name: 'GAINSIGHT_PX', + DisplayName: 'Gainsight PX', + Config: { + destConfig: { + defaultConfig: [ + 'apiKey', + 'productTagKey', + 'userAttributeMap', + 'accountAttributeMap', + 'globalContextMap', + ], + }, + excludeKeys: [], + includeKeys: [], + saveDestinationResponse: true, + secretKeys: ['apiKey', 'productTagKey'], + supportedSourceTypes: [ + 'android', + 'ios', + 'web', + 'unity', + 'amp', + 'cloud', + 'reactnative', + 'flutter', + ], + transformAt: 'router', + transformAtV1: 'router', + }, + ResponseRules: {}, + }, + Config: { + accountAttributeMap: [ + { from: 'LAST_INVOICE_DATE', to: 'last_invoice_date' }, + { from: 'LAST_INVOICE_PLAN', to: 'last_invoice_plan' }, + { from: 'LANGUAGE', to: 'language' }, + { from: 'REGION', to: 'region2' }, + { from: 'LAST_INVOICE_CURRENCY', to: 'last_invoice_currency' }, + { from: 'IBR_PLAN', to: 'ibr_plan' }, + { from: 'WH_COUNTRY', to: 'wh_country' }, + { from: 'inboxready_signup_date', to: 'inboxready_signup_date' }, + { from: 'gpt_setup', to: 'gpt_setup' }, + ], + oneTrustCookieCategories: [], + apiKey: 'sample-api-key', + eventDelivery: false, + eventDeliveryTS: 1624472902670, + globalContextMap: [{ from: 'kubrickTest', to: 'value' }], + productTagKey: 'AP-SAMPLE-2', + userAttributeMap: [{ from: 'hobbyCustomField', to: 'hobby' }], + }, + Enabled: true, + Transformations: [], + IsProcessorEnabled: true, +}; + export const data = [ { name: 'gainsight_px', @@ -463,4 +535,85 @@ export const data = [ }, }, }, + { + name: 'gainsight_px', + description: 'Test 1: Group call -- AxiosError thrown', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'group', + sentAt: '2024-02-16T06:00:54.075Z', + traits: { + name: ',sleep(100)', + REGION: 'MEA', + USERID: 'myUId', + groupId: 'myGId', + IBR_PLAN: 'free_ir', + LANGUAGE: 'EN', + gpt_setup: false, + ACCOUNT_ID: 'myGId', + WH_COUNTRY: 'MA', + LAST_INVOICE_DATE: 1706810675000, + LAST_INVOICE_PLAN: 'foundation_trial', + LAST_INVOICE_CURRENCY: 'USD', + inboxready_signup_date: 1680254544705, + }, + userId: 'myUId', + channel: 'sources', + context: { + sources: { + job_run_id: 'cn7fjonu4d9b3u706u2g', + task_run_id: 'cn7fjonu4d9b3u706u3g', + }, + }, + recordId: '111111', + rudderId: 'dummy-rudder-id', + timestamp: '2024-02-16T06:00:52.581Z', + receivedAt: '2024-02-16T06:00:52.582Z', + request_ip: '10.7.150.126', + anonymousId: 'myUId', + integrations: { limitAPIForGroup: true }, + originalTimestamp: '2024-02-16T06:00:54.075Z', + }, + metadata, + destination: destination2, + }, + ], + destType: 'gainsight_px', + }, + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + error: + '{"message":"error while fetching user: \\"403403 Forbidden\\"","destinationResponse":"403403 Forbidden"}', + statTags: { + destType: 'GAINSIGHT_PX', + destinationId: destination2.ID, + errorCategory: 'network', + errorType: 'aborted', + feature: 'router', + implementation: 'native', + module: 'destination', + }, + statusCode: 403, + metadata: [metadata], + batched: false, + destination: destination2, + }, + ], + }, + }, + }, + }, ]; From d619c9769cd270cb2d16dad0865683ff4beb2d19 Mon Sep 17 00:00:00 2001 From: AASHISH MALIK Date: Wed, 28 Feb 2024 10:17:15 +0530 Subject: [PATCH 5/5] fix(algolia): added check for objectIds or filters to be non empty (#3126) --- .../v2/destinations/algolia/procWorkflow.yaml | 2 +- .../destinations/algolia/processor/data.ts | 212 +++++++++++++++++- 2 files changed, 212 insertions(+), 2 deletions(-) diff --git a/src/cdk/v2/destinations/algolia/procWorkflow.yaml b/src/cdk/v2/destinations/algolia/procWorkflow.yaml index b9ce7ef7fd..f9ac8e3ae6 100644 --- a/src/cdk/v2/destinations/algolia/procWorkflow.yaml +++ b/src/cdk/v2/destinations/algolia/procWorkflow.yaml @@ -61,7 +61,7 @@ steps: const filters = $.context.payload.filters; const objectIDs = $.context.payload.objectIDs; $.assert(!(filters && objectIDs), "event can't have both objectIds and filters at the same time."); - $.assert(filters || objectIDs, "Either filters or objectIds is required."); + $.assert(filters.length || objectIDs.length, "Either filters or objectIds is required and must be non empty."); - name: validatePayloadForClickEvent condition: $.context.payload.eventType === "click" diff --git a/test/integrations/destinations/algolia/processor/data.ts b/test/integrations/destinations/algolia/processor/data.ts index 0cbdd8b31b..7c37c9642a 100644 --- a/test/integrations/destinations/algolia/processor/data.ts +++ b/test/integrations/destinations/algolia/processor/data.ts @@ -380,7 +380,7 @@ export const data = [ body: [ { error: - 'Either filters or objectIds is required.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required.', + 'Either filters or objectIds is required and must be non empty.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required and must be non empty.', statTags: { destType: 'ALGOLIA', errorCategory: 'dataValidation', @@ -1417,4 +1417,214 @@ export const data = [ }, }, }, + { + name: 'algolia', + description: 'Eventype must be one of click, conversion pr view', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + 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, + }, + page: { + path: '/destinations/ometria', + referrer: '', + search: '', + title: '', + url: 'https://docs.rudderstack.com/destinations/ometria', + category: 'destination', + initial_referrer: 'https://docs.rudderstack.com', + initial_referring_domain: 'docs.rudderstack.com', + }, + }, + type: 'track', + messageId: '84e26acc-56a5-4835-8233-591137fca468', + session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22', + originalTimestamp: '2019-10-14T09:03:17.562Z', + anonymousId: '123456', + event: 'product clicked', + userId: 'testuserId1', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + excludeKeys: [], + includeKeys: [], + }, + }, + Config: { + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'product clicked', + to: 'abcd', + }, + ], + }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'eventType can be either click, view or conversion: Workflow: procWorkflow, Step: preparePayload, ChildStep: undefined, OriginalError: eventType can be either click, view or conversion', + statTags: { + destType: 'ALGOLIA', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + destinationId: 'destId', + workspaceId: 'wspId', + }, + statusCode: 400, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, + { + name: 'algolia', + description: 'filters or objectIds must be non empty', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'product clicked', + sentAt: '2024-02-25T17:55:36.882Z', + userId: '12345', + channel: 'web', + properties: { + index: 'products', + list_id: 'search_results_page', + queryId: '8e737', + products: [], + eventName: 'productListView', + list_name: 'Search Results Page', + objectIds: [], + positions: [], + userToken: 'e494', + additional_attributes: {}, + }, + receivedAt: '2024-02-25T17:55:38.089Z', + request_ip: '107.130.37.100', + anonymousId: '68e9f4b8-fd4d-4c56-8ca4-858de2fd1df8', + integrations: { + All: true, + }, + originalTimestamp: '2024-02-25T17:55:36.880Z', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + excludeKeys: [], + includeKeys: [], + }, + }, + Config: { + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'product clicked', + to: 'cLick ', + }, + ], + }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Either filters or objectIds is required and must be non empty.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required and must be non empty.', + statTags: { + destType: 'ALGOLIA', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + destinationId: 'destId', + workspaceId: 'wspId', + }, + statusCode: 400, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, ];