From 53d2bef38ad5d7bc5504111ec797b3c3973546dd Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Wed, 20 Nov 2024 03:28:06 +0530 Subject: [PATCH 1/6] feat: add support for getting anonymousId by note attributes array --- .../serverSideTransform.js | 19 +++---------- .../webhookTransformations/serverSideUtlis.js | 27 ++++++++++++++++++- .../integrations/sources/shopify/constants.ts | 15 +++++++++++ .../CheckoutEventsTests.ts | 27 ++++++++++--------- .../webhookTestScenarios/GenericTrackTests.ts | 12 +++++---- 5 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js b/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js index c31bc74bf1..4f91b0e6c1 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js @@ -1,18 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ const lodash = require('lodash'); const get = require('get-value'); -// const { RedisError } = require('@rudderstack/integrations-lib'); const stats = require('../../../../util/stats'); -const { - getShopifyTopic, - // createPropertiesForEcomEvent, - extractEmailFromPayload, - getAnonymousIdAndSessionId, - // getHashLineItems, -} = require('../../../../v0/sources/shopify/util'); -// const logger = require('../../../logger'); +const { getShopifyTopic, extractEmailFromPayload } = require('../../../../v0/sources/shopify/util'); const { removeUndefinedAndNullValues, isDefinedAndNotNull } = require('../../../../v0/util'); -// const { RedisDB } = require('../../../util/redis/redisConnector'); const Message = require('../../../../v0/sources/message'); const { EventType } = require('../../../../constants'); const { @@ -28,6 +19,7 @@ const { const { createPropertiesForEcomEventFromWebhook, getProductsFromLineItems, + getAnonymousIdFromAttributes, } = require('./serverSideUtlis'); const NO_OPERATION_SUCCESS = { @@ -128,12 +120,9 @@ const processEvent = async (inputEvent, metricMetadata) => { message.setProperty('traits.email', email); } } + // attach anonymousId if the event is track event using note_attributes if (message.type !== EventType.IDENTIFY) { - const { anonymousId } = await getAnonymousIdAndSessionId( - message, - { shopifyTopic, ...metricMetadata }, - null, - ); + const anonymousId = await getAnonymousIdFromAttributes(event); if (isDefinedAndNotNull(anonymousId)) { message.setProperty('anonymousId', anonymousId); } diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js index eed03de71f..0c38dd1d10 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js @@ -1,5 +1,6 @@ +const { isDefinedAndNotNull } = require('@rudderstack/integrations-lib'); +const { v5 } = require('uuid'); const { constructPayload } = require('../../../../v0/util'); - const { lineItemsMappingJSON, productMappingJSON, @@ -39,7 +40,31 @@ const createPropertiesForEcomEventFromWebhook = (message) => { return mappedPayload; }; +/** + * Returns the anonymousId from the noteAttributes array in the webhook event + * @param {Object} event + * @returns {String} anonymousId + */ +const getAnonymousIdFromAttributes = async (event) => { + let anonymousId = null; + let cartToken = null; + const noteAttributes = event.note_attributes; + if (isDefinedAndNotNull(event) && isDefinedAndNotNull(noteAttributes)) { + const rudderAnonymousIdObject = noteAttributes.find( + (attr) => attr.name === 'rudderAnonymousId', + ); + anonymousId = rudderAnonymousIdObject ? rudderAnonymousIdObject.value : null; + const cartTokenObject = noteAttributes.find((attr) => attr.name === 'cartToken'); + cartToken = cartTokenObject ? cartTokenObject.value : null; + if (!isDefinedAndNotNull(anonymousId) && isDefinedAndNotNull(cartToken)) { + anonymousId = v5(cartToken, v5.URL); + } + } + return anonymousId; +}; + module.exports = { createPropertiesForEcomEventFromWebhook, getProductsFromLineItems, + getAnonymousIdFromAttributes, }; diff --git a/test/integrations/sources/shopify/constants.ts b/test/integrations/sources/shopify/constants.ts index f9df305841..af53a3180e 100644 --- a/test/integrations/sources/shopify/constants.ts +++ b/test/integrations/sources/shopify/constants.ts @@ -83,6 +83,21 @@ export const dummyContext = { }, }; +export const note_attributes = [ + { + name: 'cartId', + value: '9c623f099fc8819aa4d6a958b65dfe7d', + }, + { + name: 'cartToken', + value: 'Z2NwLXVzLWVhc3QxOjAxSkQzNUFXVEI4VkVUNUpTTk1LSzBCMzlF', + }, + { + name: 'rudderAnonymousId', + value: '50ead33e-d763-4854-b0ab-765859ef05cb', + }, +]; + export const responseDummyContext = { document: { location: { diff --git a/test/integrations/sources/shopify/webhookTestScenarios/CheckoutEventsTests.ts b/test/integrations/sources/shopify/webhookTestScenarios/CheckoutEventsTests.ts index ade496efb7..a154ccb890 100644 --- a/test/integrations/sources/shopify/webhookTestScenarios/CheckoutEventsTests.ts +++ b/test/integrations/sources/shopify/webhookTestScenarios/CheckoutEventsTests.ts @@ -1,7 +1,6 @@ // This file contains the test scenarios for the server-side events from the Shopify GraphQL API for // the v1 transformation flow -import { mockFns } from '../mocks'; -import { dummySourceConfig } from '../constants'; +import { dummySourceConfig, note_attributes } from '../constants'; export const checkoutEventsTestScenarios = [ { @@ -27,7 +26,7 @@ export const checkoutEventsTestScenarios = [ updated_at: '2024-11-05T21:22:02-05:00', landing_site: '/', note: '', - note_attributes: [], + note_attributes, referring_site: '', shipping_lines: [], shipping_address: [], @@ -145,7 +144,7 @@ export const checkoutEventsTestScenarios = [ updated_at: '2024-11-05T21:22:02-05:00', landing_site: '/', note: '', - note_attributes: [], + note_attributes, referring_site: '', shipping_lines: [], shipping_address: [], @@ -237,7 +236,7 @@ export const checkoutEventsTestScenarios = [ traits: { shippingAddress: [], }, - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', }, ], }, @@ -269,7 +268,7 @@ export const checkoutEventsTestScenarios = [ created_at: '2024-09-16T03:50:1500:00', updated_at: '2024-09-17T03:29:02-04:00', note: '', - note_attributes: [], + note_attributes, shipping_address: { first_name: 'testuser', address1: 'oakwood bridge', @@ -396,7 +395,7 @@ export const checkoutEventsTestScenarios = [ output: { batch: [ { - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', context: { cart_token: 'Z2NwLXVzLWVhc3QxOjAxSjdXRjdOQjY0NlFFNFdQVEg0MTRFM1E2', integration: { @@ -415,7 +414,7 @@ export const checkoutEventsTestScenarios = [ created_at: '2024-09-16T03:50:1500:00', updated_at: '2024-09-17T03:29:02-04:00', note: '', - note_attributes: [], + note_attributes, shipping_address: { first_name: 'testuser', address1: 'oakwood bridge', @@ -680,7 +679,7 @@ export const checkoutEventsTestScenarios = [ merchant_of_record_app_id: null, name: '#1017', note: null, - note_attributes: [], + note_attributes, number: 17, order_number: 1017, order_status_url: @@ -988,7 +987,7 @@ export const checkoutEventsTestScenarios = [ merchant_of_record_app_id: null, name: '#1017', note: null, - note_attributes: [], + note_attributes, number: 17, order_number: 1017, order_status_url: @@ -1289,7 +1288,7 @@ export const checkoutEventsTestScenarios = [ }, }, timestamp: '2024-11-06T02:54:50.000Z', - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', }, ], }, @@ -1326,6 +1325,7 @@ export const checkoutEventsTestScenarios = [ current_total_tax: '0.00', email: 'henry@wfls.com', name: '#1017', + note_attributes, order_number: 1017, order_status_url: 'https://pixel-testing-rs.myshopify.com/59026964593/orders/676613a0027fc8240e16d67fdc9f5ac8/authenticate?key=a70bbe7ec8abcc46b77e4331e4df8c60', @@ -1486,6 +1486,7 @@ export const checkoutEventsTestScenarios = [ current_total_tax: '0.00', email: 'henry@wfls.com', name: '#1017', + note_attributes, order_number: 1017, order_status_url: 'https://pixel-testing-rs.myshopify.com/59026964593/orders/676613a0027fc8240e16d67fdc9f5ac8/authenticate?key=a70bbe7ec8abcc46b77e4331e4df8c60', @@ -1675,7 +1676,7 @@ export const checkoutEventsTestScenarios = [ }, }, timestamp: '2024-11-06T02:54:50.000Z', - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', }, ], }, @@ -1684,4 +1685,4 @@ export const checkoutEventsTestScenarios = [ }, }, }, -].map((d1) => ({ ...d1, mockFns })); +]; diff --git a/test/integrations/sources/shopify/webhookTestScenarios/GenericTrackTests.ts b/test/integrations/sources/shopify/webhookTestScenarios/GenericTrackTests.ts index f04fd7e08e..d68d0a8f59 100644 --- a/test/integrations/sources/shopify/webhookTestScenarios/GenericTrackTests.ts +++ b/test/integrations/sources/shopify/webhookTestScenarios/GenericTrackTests.ts @@ -1,7 +1,7 @@ // This file contains the test scenarios for the server-side events from the Shopify GraphQL API for // the v1 transformation flow import { mockFns } from '../mocks'; -import { dummySourceConfig } from '../constants'; +import { dummySourceConfig, note_attributes } from '../constants'; export const genericTrackTestScenarios = [ { @@ -24,6 +24,7 @@ export const genericTrackTestScenarios = [ token: 'Z2NwLXVzLWVhc3QxOjAxSjdXRjdOQjY0NlFFNFdQVEg0MTRFM1E2', line_items: [], note: '', + note_attributes, updated_at: '2024-09-17T08:15:13.280Z', created_at: '2024-09-16T03:50:15.478Z', }, @@ -43,7 +44,7 @@ export const genericTrackTestScenarios = [ output: { batch: [ { - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', context: { integration: { name: 'SHOPIFY', @@ -58,6 +59,7 @@ export const genericTrackTestScenarios = [ id: 'Z2NwLXVzLWVhc3QxOjAxSjdXRjdOQjY0NlFFNFdQVEg0MTRFM1E2', line_items: [], note: '', + note_attributes, token: 'Z2NwLXVzLWVhc3QxOjAxSjdXRjdOQjY0NlFFNFdQVEg0MTRFM1E2', updated_at: '2024-09-17T08:15:13.280Z', }, @@ -149,7 +151,7 @@ export const genericTrackTestScenarios = [ merchant_of_record_app_id: null, name: '#1017', note: null, - note_attributes: [], + note_attributes, order_number: 1017, original_total_additional_fees_set: null, original_total_duties_set: null, @@ -363,7 +365,7 @@ export const genericTrackTestScenarios = [ merchant_of_record_app_id: null, name: '#1017', note: null, - note_attributes: [], + note_attributes, order_number: 1017, original_total_additional_fees_set: null, original_total_duties_set: null, @@ -545,7 +547,7 @@ export const genericTrackTestScenarios = [ traits: { email: 'henry@wfls.com', }, - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', }, ], }, From a0d4efcb165e61c27a96a2d436cd3af625522842 Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Fri, 22 Nov 2024 01:09:32 +0530 Subject: [PATCH 2/6] chore: allow only anonymousId set from note attributes --- .../shopify/webhookTransformations/serverSideUtlis.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js index 0c38dd1d10..1ac60f967d 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js @@ -1,5 +1,4 @@ const { isDefinedAndNotNull } = require('@rudderstack/integrations-lib'); -const { v5 } = require('uuid'); const { constructPayload } = require('../../../../v0/util'); const { lineItemsMappingJSON, @@ -47,18 +46,12 @@ const createPropertiesForEcomEventFromWebhook = (message) => { */ const getAnonymousIdFromAttributes = async (event) => { let anonymousId = null; - let cartToken = null; const noteAttributes = event.note_attributes; if (isDefinedAndNotNull(event) && isDefinedAndNotNull(noteAttributes)) { const rudderAnonymousIdObject = noteAttributes.find( (attr) => attr.name === 'rudderAnonymousId', ); anonymousId = rudderAnonymousIdObject ? rudderAnonymousIdObject.value : null; - const cartTokenObject = noteAttributes.find((attr) => attr.name === 'cartToken'); - cartToken = cartTokenObject ? cartTokenObject.value : null; - if (!isDefinedAndNotNull(anonymousId) && isDefinedAndNotNull(cartToken)) { - anonymousId = v5(cartToken, v5.URL); - } } return anonymousId; }; From 83c8f52d76739b46f558c2d8682f4b831f9c701a Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:23:00 +0530 Subject: [PATCH 3/6] chore: add unit tests --- .../serverSideUtils.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideUtils.test.js b/src/v1/sources/shopify/webhookTransformations/serverSideUtils.test.js index a611d1d8dc..4a2839103b 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideUtils.test.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideUtils.test.js @@ -1,6 +1,7 @@ const { getProductsFromLineItems, createPropertiesForEcomEventFromWebhook, + getAnonymousIdFromAttributes, } = require('./serverSideUtlis'); const { constructPayload } = require('../../../../v0/util'); @@ -109,4 +110,21 @@ describe('serverSideUtils.js', () => { }); }); }); + + describe('getAnonymousIdFromAttributes', () => { + // Handles empty note_attributes array gracefully + it('should return null when note_attributes is an empty array', async () => { + const event = { note_attributes: [] }; + const result = await getAnonymousIdFromAttributes(event); + expect(result).toBeNull(); + }); + + it('get anonymousId from noteAttributes', async () => { + const event = { + note_attributes: [{ name: 'rudderAnonymousId', value: '123456' }], + }; + const result = await getAnonymousIdFromAttributes(event); + expect(result).toEqual('123456'); + }); + }); }); From 70fa123a771914d6fc1387e7e6ebc0b2e4db4d5d Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:32:08 +0530 Subject: [PATCH 4/6] chore: address initialization comment --- .../sources/shopify/webhookTransformations/serverSideUtlis.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js index 1ac60f967d..0afbf30277 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js @@ -46,8 +46,8 @@ const createPropertiesForEcomEventFromWebhook = (message) => { */ const getAnonymousIdFromAttributes = async (event) => { let anonymousId = null; - const noteAttributes = event.note_attributes; - if (isDefinedAndNotNull(event) && isDefinedAndNotNull(noteAttributes)) { + if (isDefinedAndNotNull(event) && isDefinedAndNotNull(event.note_attributes)) { + const noteAttributes = event.note_attributes; const rudderAnonymousIdObject = noteAttributes.find( (attr) => attr.name === 'rudderAnonymousId', ); From 3eb5c551f6551e8da9451787159d5616114aa1c8 Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:50:06 +0530 Subject: [PATCH 5/6] chore: update function as per review suggestion --- .../webhookTransformations/serverSideUtlis.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js index 0afbf30277..59a1ddc04f 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js @@ -45,15 +45,14 @@ const createPropertiesForEcomEventFromWebhook = (message) => { * @returns {String} anonymousId */ const getAnonymousIdFromAttributes = async (event) => { - let anonymousId = null; - if (isDefinedAndNotNull(event) && isDefinedAndNotNull(event.note_attributes)) { - const noteAttributes = event.note_attributes; - const rudderAnonymousIdObject = noteAttributes.find( - (attr) => attr.name === 'rudderAnonymousId', - ); - anonymousId = rudderAnonymousIdObject ? rudderAnonymousIdObject.value : null; + if (!isDefinedAndNotNull(event) || !isDefinedAndNotNull(event.note_attributes)) { + return null; // Return early if event or note_attributes is invalid } - return anonymousId; + + const noteAttributes = event.note_attributes; + const rudderAnonymousIdObject = noteAttributes.find((attr) => attr.name === 'rudderAnonymousId'); + + return rudderAnonymousIdObject ? rudderAnonymousIdObject.value : null; }; module.exports = { From b3f7140cd9444e74955bf91200dc7c3150ce335e Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:32:04 +0530 Subject: [PATCH 6/6] chore: fix sonar issues --- .../shopify/webhookTransformations/serverSideTransform.js | 2 +- .../sources/shopify/webhookTransformations/serverSideUtlis.js | 3 +-- src/v1/sources/shopify/webpixelTransformations/pixelUtils.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js b/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js index 4f91b0e6c1..292a980a93 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js @@ -122,7 +122,7 @@ const processEvent = async (inputEvent, metricMetadata) => { } // attach anonymousId if the event is track event using note_attributes if (message.type !== EventType.IDENTIFY) { - const anonymousId = await getAnonymousIdFromAttributes(event); + const anonymousId = getAnonymousIdFromAttributes(event); if (isDefinedAndNotNull(anonymousId)) { message.setProperty('anonymousId', anonymousId); } diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js index 59a1ddc04f..951fa479e4 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js @@ -17,7 +17,6 @@ const getProductsFromLineItems = (lineItems, mapping) => { } const products = []; lineItems.forEach((lineItem) => { - // const product = constructPayload(lineItem, lineItemsMappingJSON); const product = constructPayload(lineItem, mapping); products.push(product); }); @@ -44,7 +43,7 @@ const createPropertiesForEcomEventFromWebhook = (message) => { * @param {Object} event * @returns {String} anonymousId */ -const getAnonymousIdFromAttributes = async (event) => { +const getAnonymousIdFromAttributes = (event) => { if (!isDefinedAndNotNull(event) || !isDefinedAndNotNull(event.note_attributes)) { return null; // Return early if event or note_attributes is invalid } diff --git a/src/v1/sources/shopify/webpixelTransformations/pixelUtils.js b/src/v1/sources/shopify/webpixelTransformations/pixelUtils.js index 0c1007f311..46ae59e0cf 100644 --- a/src/v1/sources/shopify/webpixelTransformations/pixelUtils.js +++ b/src/v1/sources/shopify/webpixelTransformations/pixelUtils.js @@ -14,7 +14,7 @@ const { function getNestedValue(object, path) { const keys = path.split('.'); - return keys.reduce((nestedObject, key) => nestedObject && nestedObject[key], object); + return keys.reduce((nestedObject, key) => nestedObject?.[key], object); } function setNestedValue(object, path, value) {