From 3ef710f1cbd22f44483d079ba53da1b9aca8363a Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 24 Oct 2024 16:17:26 -0400 Subject: [PATCH 1/7] fix: Support capturing click ids as custom flags in commerce events --- src/ecommerce.js | 3 +- test/src/config/utils.js | 2 + test/src/tests-integration-capture.ts | 204 +++++++++++++++++++++++++- 3 files changed, 207 insertions(+), 2 deletions(-) diff --git a/src/ecommerce.js b/src/ecommerce.js index 585f0d8e..bc94368e 100644 --- a/src/ecommerce.js +++ b/src/ecommerce.js @@ -524,6 +524,7 @@ export default function Ecommerce(mpInstance) { }; this.createCommerceEventObject = function(customFlags, options) { + const { extend } = mpInstance._Helpers; var baseEvent; mpInstance.Logger.verbose( @@ -539,7 +540,7 @@ export default function Ecommerce(mpInstance) { baseEvent.CurrencyCode = mpInstance._Store.currencyCode; baseEvent.ShoppingCart = []; - baseEvent.CustomFlags = customFlags; + baseEvent.CustomFlags = extend(baseEvent.CustomFlags, customFlags); return baseEvent; } else { diff --git a/test/src/config/utils.js b/test/src/config/utils.js index 46e15f84..2c95c000 100644 --- a/test/src/config/utils.js +++ b/test/src/config/utils.js @@ -176,6 +176,8 @@ var pluses = /\+/g, if (batch.events.length) { return batch.events.find(function(event) { switch (event.event_type) { + case 'screen_view': + return event.data.screen_name === eventName; case 'commerce_event': if (event.data.product_action) { return event.data.product_action.action === eventName; diff --git a/test/src/tests-integration-capture.ts b/test/src/tests-integration-capture.ts index 7b68034a..8b2025a2 100644 --- a/test/src/tests-integration-capture.ts +++ b/test/src/tests-integration-capture.ts @@ -8,7 +8,7 @@ const { waitForCondition, fetchMockSuccess, deleteAllCookies, findEventFromReque const mParticle = window.mParticle; -describe('Integration Capture', () => { +describe.only('Integration Capture', () => { beforeEach(() => { mParticle._resetForTests(MPConfig); fetchMock.post(urls.events, 200); @@ -64,4 +64,206 @@ describe('Integration Capture', () => { 'Facebook.BrowserId': '54321', }); }); + + it('should add captured integrations to event custom flags, prioritizing passed in custom flags', async () => { + await waitForCondition(hasIdentifyReturned); + window.mParticle.logEvent( + 'Test Event', + mParticle.EventType.Navigation, + { mykey: 'myvalue' }, + {'Facebook.ClickId': 'passed-in',} + ); + + const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Event'); + + const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; + + expect(testEvent).to.have.property('data'); + expect(testEvent.data).to.have.property('event_name', 'Test Event'); + expect(testEvent.data).to.have.property('custom_flags'); + expect(testEvent.data.custom_flags).to.deep.equal({ + 'Facebook.ClickId': 'passed-in', + 'Facebook.BrowserId': '54321', + }); + }); + + it('should add captured integrations to page view custom flags', async () => { + await waitForCondition(hasIdentifyReturned); + + window.mParticle.logPageView( + 'Test Page View', + {'foo-attr': 'bar-attr'} + ); + + const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Page View'); + + const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; + + expect(testEvent).to.have.property('data'); + expect(testEvent.data).to.have.property('screen_name', 'Test Page View'); + expect(testEvent.data).to.have.property('custom_flags'); + expect(testEvent.data.custom_flags).to.deep.equal({ + 'Facebook.ClickId': `fb.1.${initialTimestamp}.1234`, + 'Facebook.BrowserId': '54321', + }); + }); + + it('should add captured integrations to page view custom flags, prioritizing passed in custom flags', async () => { + await waitForCondition(hasIdentifyReturned); + window.mParticle.logPageView( + 'Test Page View', + {'foo-attr': 'bar-attr'}, + {'Facebook.ClickId': 'passed-in',} + ); + + const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Page View'); + + expect(testEvent).to.have.property('data'); + expect(testEvent.data).to.have.property('screen_name', 'Test Page View'); + expect(testEvent.data).to.have.property('custom_flags'); + expect(testEvent.data.custom_flags).to.deep.equal({ + 'Facebook.ClickId': 'passed-in', + 'Facebook.BrowserId': '54321', + }); + }); + + it('should add captured integrations to commerce event custom flags', async () => { + await waitForCondition(hasIdentifyReturned); + + const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); + const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); + + const transactionAttributes = { + Id: 'foo-transaction-id', + Revenue: 430.00, + Tax: 30 + }; + + const customAttributes = {sale: true}; + const customFlags = {foo: 'bar'}; + + mParticle.eCommerce.logProductAction( + mParticle.ProductActionType.Purchase, + [product1, product2], + customAttributes, + customFlags, + transactionAttributes); + + const testEvent = findEventFromRequest(fetchMock.calls(), 'purchase'); + + const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; + + expect(testEvent.data.product_action).to.have.property('action', 'purchase'); + expect(testEvent.data).to.have.property('custom_flags'); + expect(testEvent.data.custom_flags).to.deep.equal({ + foo: 'bar', + 'Facebook.ClickId': `fb.1.${initialTimestamp}.1234`, + 'Facebook.BrowserId': '54321', + }); + }); + + it('should add captured integrations to commerce event custom flags, prioritizing passed in flags', async () => { + await waitForCondition(hasIdentifyReturned); + + const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); + const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); + + const transactionAttributes = { + Id: 'foo-transaction-id', + Revenue: 430.00, + Tax: 30 + }; + + const customAttributes = {sale: true}; + const customFlags = { + 'Facebook.ClickId': 'passed-in' + }; + + mParticle.eCommerce.logProductAction( + mParticle.ProductActionType.Purchase, + [product1, product2], + customAttributes, + customFlags, + transactionAttributes); + + + const testEvent = findEventFromRequest(fetchMock.calls(), 'purchase'); + + expect(testEvent.data.product_action).to.have.property('action', 'purchase'); + expect(testEvent.data).to.have.property('custom_flags'); + expect(testEvent.data.custom_flags).to.deep.equal({ + 'Facebook.ClickId': 'passed-in', + 'Facebook.BrowserId': '54321', + }); + }); + + it('should add captured integrations to commerce event custom flags', async () => { + await waitForCondition(hasIdentifyReturned); + + const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); + const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); + + const transactionAttributes = { + Id: 'foo-transaction-id', + Revenue: 430.00, + Tax: 30 + }; + + const customAttributes = {sale: true}; + const customFlags = {foo: 'bar'}; + + mParticle.eCommerce.logProductAction( + mParticle.ProductActionType.Purchase, + [product1, product2], + customAttributes, + customFlags, + transactionAttributes); + + const testEvent = findEventFromRequest(fetchMock.calls(), 'purchase'); + + const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; + + expect(testEvent.data.product_action).to.have.property('action', 'purchase'); + expect(testEvent.data).to.have.property('custom_flags'); + expect(testEvent.data.custom_flags).to.deep.equal({ + foo: 'bar', + 'Facebook.ClickId': `fb.1.${initialTimestamp}.1234`, + 'Facebook.BrowserId': '54321', + }); + }); + + it('should add captured integrations to commerce event custom flags, prioritizing passed in flags', async () => { + await waitForCondition(hasIdentifyReturned); + + const product1 = mParticle.eCommerce.createProduct('iphone', 'iphoneSKU', 999, 1); + const product2 = mParticle.eCommerce.createProduct('galaxy', 'galaxySKU', 799, 1); + + const transactionAttributes = { + Id: 'foo-transaction-id', + Revenue: 430.00, + Tax: 30 + }; + + const customAttributes = {sale: true}; + const customFlags = { + 'Facebook.ClickId': 'passed-in' + }; + + mParticle.eCommerce.logProductAction( + mParticle.ProductActionType.Purchase, + [product1, product2], + customAttributes, + customFlags, + transactionAttributes); + + + const testEvent = findEventFromRequest(fetchMock.calls(), 'purchase'); + + expect(testEvent.data.product_action).to.have.property('action', 'purchase'); + expect(testEvent.data).to.have.property('custom_flags'); + expect(testEvent.data.custom_flags).to.deep.equal({ + 'Facebook.ClickId': 'passed-in', + 'Facebook.BrowserId': '54321', + }); + }); }); \ No newline at end of file From deda491bafeefa6c428f50363adc56d48ad15662 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 24 Oct 2024 16:36:04 -0400 Subject: [PATCH 2/7] Apply suggestions from code review Co-authored-by: Alex S <49695018+alexs-mparticle@users.noreply.github.com> --- test/src/tests-integration-capture.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/src/tests-integration-capture.ts b/test/src/tests-integration-capture.ts index 8b2025a2..9b6cf403 100644 --- a/test/src/tests-integration-capture.ts +++ b/test/src/tests-integration-capture.ts @@ -8,7 +8,7 @@ const { waitForCondition, fetchMockSuccess, deleteAllCookies, findEventFromReque const mParticle = window.mParticle; -describe.only('Integration Capture', () => { +describe('Integration Capture', () => { beforeEach(() => { mParticle._resetForTests(MPConfig); fetchMock.post(urls.events, 200); @@ -71,12 +71,11 @@ describe.only('Integration Capture', () => { 'Test Event', mParticle.EventType.Navigation, { mykey: 'myvalue' }, - {'Facebook.ClickId': 'passed-in',} + { 'Facebook.ClickId': 'passed-in' }, ); const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Event'); - const initialTimestamp = window.mParticle.getInstance()._IntegrationCapture.initialTimestamp; expect(testEvent).to.have.property('data'); expect(testEvent.data).to.have.property('event_name', 'Test Event'); @@ -113,7 +112,7 @@ describe.only('Integration Capture', () => { window.mParticle.logPageView( 'Test Page View', {'foo-attr': 'bar-attr'}, - {'Facebook.ClickId': 'passed-in',} + {'Facebook.ClickId': 'passed-in'}, ); const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Page View'); From 6f6228ac32b675532b987da527a58584fb156cef Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 24 Oct 2024 16:40:54 -0400 Subject: [PATCH 3/7] refactor extend to use spread operator --- src/ecommerce.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ecommerce.js b/src/ecommerce.js index bc94368e..4a0382af 100644 --- a/src/ecommerce.js +++ b/src/ecommerce.js @@ -524,7 +524,6 @@ export default function Ecommerce(mpInstance) { }; this.createCommerceEventObject = function(customFlags, options) { - const { extend } = mpInstance._Helpers; var baseEvent; mpInstance.Logger.verbose( @@ -540,7 +539,10 @@ export default function Ecommerce(mpInstance) { baseEvent.CurrencyCode = mpInstance._Store.currencyCode; baseEvent.ShoppingCart = []; - baseEvent.CustomFlags = extend(baseEvent.CustomFlags, customFlags); + baseEvent.CustomFlags = { + ...baseEvent.CustomFlags, + ...customFlags, + }; return baseEvent; } else { From 46a1c55693dca347d6f26dcbba17299b9b729028 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 24 Oct 2024 17:42:22 -0400 Subject: [PATCH 4/7] Fix tests --- src/ecommerce.js | 4 ++-- test/src/config/utils.js | 4 ++-- test/src/tests-event-logging.js | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ecommerce.js b/src/ecommerce.js index 4a0382af..83758aec 100644 --- a/src/ecommerce.js +++ b/src/ecommerce.js @@ -540,8 +540,8 @@ export default function Ecommerce(mpInstance) { baseEvent.CurrencyCode = mpInstance._Store.currencyCode; baseEvent.ShoppingCart = []; baseEvent.CustomFlags = { - ...baseEvent.CustomFlags, - ...customFlags, + ...(baseEvent.CustomFlags || {}), + ...(customFlags || {}), }; return baseEvent; diff --git a/test/src/config/utils.js b/test/src/config/utils.js index 2c95c000..2995a307 100644 --- a/test/src/config/utils.js +++ b/test/src/config/utils.js @@ -177,7 +177,8 @@ var pluses = /\+/g, return batch.events.find(function(event) { switch (event.event_type) { case 'screen_view': - return event.data.screen_name === eventName; + // The SDK sets "PageView" as the default for a screen_name if one is not provided + return ['PageView', eventName].indexOf(event.data.screen_name) > -1; case 'commerce_event': if (event.data.product_action) { return event.data.product_action.action === eventName; @@ -234,7 +235,6 @@ var pluses = /\+/g, return null; } var batch = JSON.parse(request[1].body); - if (!batch.events) { return null; } diff --git a/test/src/tests-event-logging.js b/test/src/tests-event-logging.js index aa942b85..14c0cae3 100644 --- a/test/src/tests-event-logging.js +++ b/test/src/tests-event-logging.js @@ -427,7 +427,7 @@ describe('event logging', function() { const pageViewEvent = findEventFromRequest( fetchMock.calls(), - 'screen_view' + 'My Page View' ); Should(pageViewEvent).be.ok(); @@ -456,7 +456,7 @@ describe('event logging', function() { const pageViewEvent = findEventFromRequest( fetchMock.calls(), - 'screen_view' + 'test' ); Should(pageViewEvent).be.ok(); @@ -480,7 +480,7 @@ describe('event logging', function() { const pageViewEvent = findEventFromRequest( fetchMock.calls(), - 'screen_view' + 'test bypass' ); Should(pageViewEvent).not.be.ok(); @@ -494,7 +494,7 @@ describe('event logging', function() { mParticle.logPageView('test1', 'invalid', null); const pageViewEvent = findEventFromRequest( fetchMock.calls(), - 'screen_view' + 'test1' ); Should(pageViewEvent).not.be.ok(); @@ -510,7 +510,7 @@ describe('event logging', function() { const pageViewEvent = findEventFromRequest( fetchMock.calls(), - 'screen_view' + 'test' ); Should(pageViewEvent).not.be.ok(); @@ -518,7 +518,7 @@ describe('event logging', function() { }) }); - it('should log event with name PageView when an invalid event name is passed', function(done) { + it.only('should log event with name PageView when an invalid event name is passed', function(done) { waitForCondition(hasIdentifyReturned) .then(() => { fetchMock.resetHistory(); From bdb0dde07e3b3394a3f3e618634ef730151b78c8 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 24 Oct 2024 17:42:56 -0400 Subject: [PATCH 5/7] Remove .only --- test/src/tests-event-logging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/tests-event-logging.js b/test/src/tests-event-logging.js index 14c0cae3..6c52f657 100644 --- a/test/src/tests-event-logging.js +++ b/test/src/tests-event-logging.js @@ -518,7 +518,7 @@ describe('event logging', function() { }) }); - it.only('should log event with name PageView when an invalid event name is passed', function(done) { + it('should log event with name PageView when an invalid event name is passed', function(done) { waitForCondition(hasIdentifyReturned) .then(() => { fetchMock.resetHistory(); From f170f4fb629276707bc089a0152a4f2711921a70 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Fri, 25 Oct 2024 09:50:44 -0400 Subject: [PATCH 6/7] prefer extend instead of spread inside of js --- src/ecommerce.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ecommerce.js b/src/ecommerce.js index 83758aec..2b6b9208 100644 --- a/src/ecommerce.js +++ b/src/ecommerce.js @@ -525,6 +525,8 @@ export default function Ecommerce(mpInstance) { this.createCommerceEventObject = function(customFlags, options) { var baseEvent; + // https://go.mparticle.com/work/SQDSDKS-4801 + var { extend } = mpInstance._Helpers; mpInstance.Logger.verbose( Messages.InformationMessages.StartingLogCommerceEvent @@ -539,10 +541,7 @@ export default function Ecommerce(mpInstance) { baseEvent.CurrencyCode = mpInstance._Store.currencyCode; baseEvent.ShoppingCart = []; - baseEvent.CustomFlags = { - ...(baseEvent.CustomFlags || {}), - ...(customFlags || {}), - }; + baseEvent.CustomFlags = extend(baseEvent.CustomFlags, customFlags); return baseEvent; } else { From 3ab029a11f6b510011b49d401d1a8af1420e86fb Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Fri, 25 Oct 2024 11:10:52 -0400 Subject: [PATCH 7/7] update tests --- test/src/config/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/config/utils.js b/test/src/config/utils.js index 2995a307..06b6476a 100644 --- a/test/src/config/utils.js +++ b/test/src/config/utils.js @@ -172,13 +172,14 @@ var pluses = /\+/g, return mParticle.getInstance()._Persistence.getLocalStorage(); } }, + // https://go.mparticle.com/work/SQDSDKS-6894 findEventFromBatch = function(batch, eventName) { if (batch.events.length) { return batch.events.find(function(event) { switch (event.event_type) { case 'screen_view': // The SDK sets "PageView" as the default for a screen_name if one is not provided - return ['PageView', eventName].indexOf(event.data.screen_name) > -1; + return ['PageView', eventName].includes(event.data.screen_name); case 'commerce_event': if (event.data.product_action) { return event.data.product_action.action === eventName;