From 3e96281586e839ee091a52fb21739909b143e5c9 Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Wed, 29 Sep 2021 21:40:37 -0400 Subject: [PATCH 1/7] Add built forwarder js to html test runner --- test/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.html b/test/index.html index d342ce9..5cc6446 100644 --- a/test/index.html +++ b/test/index.html @@ -21,7 +21,7 @@ window.mParticle = new mp(); - + From 30c7fe26ebb23e283880bde4af209d1297d2b404 Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Wed, 29 Sep 2021 21:54:24 -0400 Subject: [PATCH 2/7] Implement gtag as an alternative to conversion async --- src/GoogleAdWordsEventForwarder.js | 236 ++++++++--- test/tests.js | 637 +++++++++++++++++++++-------- 2 files changed, 655 insertions(+), 218 deletions(-) diff --git a/src/GoogleAdWordsEventForwarder.js b/src/GoogleAdWordsEventForwarder.js index f1bc5a6..c79b264 100644 --- a/src/GoogleAdWordsEventForwarder.js +++ b/src/GoogleAdWordsEventForwarder.js @@ -35,7 +35,8 @@ labels, customAttributeMappings, reportingService, - eventQueue = []; + eventQueue = [], + gtagSiteId; self.name = name; @@ -70,51 +71,151 @@ return 'Can\'t send to forwarder ' + name + ', not initialized'; } - function sendOrQueueEvent(adWordEvent) { - if (window.google_trackConversion) { - window.google_trackConversion(adWordEvent); + // Converts an mParticle Event into either Legacy or gtag Event + function generateEvent(mPEvent, conversionLabel, isPageEvent) { + if (window.gtag && forwarderSettings.enableGtag == 'True') { + return generateGtagEvent(mPEvent, conversionLabel, isPageEvent); + } else if (window.google_trackConversion) { + return generateAdwordsEvent(mPEvent, conversionLabel, isPageEvent); } else { - eventQueue.push(adWordEvent); + console.error('Unrecognized Event', mPEvent); + return false; } } - function logCommerce(event) { - if (event.ProductAction - && event.ProductAction.ProductList - && event.ProductAction.ProductActionType) { - - var isPageEvent = false; - var conversionLabel = getConversionLabel(event, isPageEvent); - - if (typeof (conversionLabel) !== 'string') { + // Converts an mParticle Commerce Event into either Legacy or gtag Event + function generateCommerceEvent(mPEvent, conversionLabel, isPageEvent) { + if (mPEvent.ProductAction + && mPEvent.ProductAction.ProductList + && mPEvent.ProductAction.ProductActionType) { + + if (window.gtag && forwarderSettings.enableGtag == 'True') { + return generateGtagCommerceEvent(mPEvent, conversionLabel, isPageEvent); + } else if (window.google_trackConversion) { + return generateAdwordsCommerceEvent(mPEvent, conversionLabel, isPageEvent); + } else { + console.error('Unrecognized Commerce Event', mPEvent); return false; } + + } else { + return false; + } + } + + + // ** Adwords Events + + function getBaseAdWordEvent() { + var adWordEvent = {}; + adWordEvent.google_conversion_value = 0; + adWordEvent.google_conversion_language = 'en'; + adWordEvent.google_conversion_format = '3'; + adWordEvent.google_conversion_color = 'ffffff'; + adWordEvent.google_remarketing_only = forwarderSettings.remarketingOnly == 'True'; + adWordEvent.google_conversion_id = forwarderSettings.conversionId; + return adWordEvent; + } + + function generateAdwordsEvent(mPEvent, conversionLabel, isPageEvent) { + var adWordEvent = getBaseAdWordEvent(); + adWordEvent.google_conversion_label = conversionLabel; + adWordEvent.google_custom_params = getCustomProps(mPEvent, isPageEvent); - var adWordEvent = getBaseAdWordEvent(); - adWordEvent.google_conversion_label = conversionLabel; + return adWordEvent; + } + + function generateAdwordsCommerceEvent(mPEvent, conversionLabel, isPageEvent) { + var adWordEvent = getBaseAdWordEvent(); + adWordEvent.google_conversion_label = conversionLabel; + + if (mPEvent.ProductAction.ProductActionType === mParticle.ProductActionType.Purchase + && mPEvent.ProductAction.TransactionId) { + adWordEvent.google_conversion_order_id = mPEvent.ProductAction.TransactionId; + } + + if (mPEvent.CurrencyCode) { + adWordEvent.google_conversion_currency = mPEvent.CurrencyCode; + } + if (mPEvent.ProductAction.TotalAmount) { + adWordEvent.google_conversion_value = mPEvent.ProductAction.TotalAmount; + } + adWordEvent.google_custom_params = getCustomProps(mPEvent, isPageEvent); + return adWordEvent; + } + + // gtag Events + function generateGtagEvent(mPEvent, conversionLabel, isPageEvent) { + var conversionPayload = { + 'send-to': gtagSiteId + '/' + conversionLabel + }; + + var customProps = getCustomProps(mPEvent, isPageEvent); + + var payload = Object.assign({}, conversionPayload, customProps); + + return payload; + } + + function generateGtagCommerceEvent(mPEvent, conversionLabel, isPageEvent) { + var conversionPayload = { + 'send-to': gtagSiteId + '/' + conversionLabel + }; + + var customProps = getCustomProps(mPEvent, isPageEvent); + + if (mPEvent.ProductAction.ProductActionType === mParticle.ProductActionType.Purchase + && mPEvent.ProductAction.TransactionId) { if (event.ProductAction.ProductActionType === mParticle.ProductActionType.Purchase && event.ProductAction.TransactionId) { adWordEvent.google_conversion_order_id = event.ProductAction.TransactionId; } + conversionPayload.order_id = mPEvent.ProductAction.TransactionId; + } - if (event.CurrencyCode) { - adWordEvent.google_conversion_currency = event.CurrencyCode; - } + if (mPEvent.CurrencyCode) { + conversionPayload.currency = mPEvent.CurrencyCode; + } - if (event.ProductAction.TotalAmount) { - adWordEvent.google_conversion_value = event.ProductAction.TotalAmount; - } + if (mPEvent.ProductAction.TotalAmount) { + conversionPayload.value = mPEvent.ProductAction.TotalAmount; + } + + var payload = Object.assign({}, conversionPayload, customProps); + + return payload; + } - adWordEvent.google_custom_params = getCustomProps(event, isPageEvent); + // Sends final event to Google or queues if Google isn't ready + function sendOrQueueEvent(conversionPayload) { + if (window.gtag && forwarderSettings.enableGtag == 'True') { + gtag('event', 'conversion', conversionPayload); + } else if (window.google_trackConversion) { + window.google_trackConversion(conversionPayload); + } else { + eventQueue.push(conversionPayload); + } + } - sendOrQueueEvent(adWordEvent); + function logCommerce(event, isPageEvent) { + var isPageEvent = false; + var conversionLabel = getConversionLabel(event, isPageEvent); - return true; + if (typeof (conversionLabel) !== 'string') { + return false; } - return false; + var eventPayload = generateCommerceEvent(event, conversionLabel, isPageEvent); + + if (!eventPayload) { + return false; + } + + sendOrQueueEvent(eventPayload); + + return true; } function logPageEvent(event, isPageEvent) { @@ -123,26 +224,19 @@ return false; } - var adWordEvent = getBaseAdWordEvent(); - adWordEvent.google_conversion_label = conversionLabel; - adWordEvent.google_custom_params = getCustomProps(event, isPageEvent); + var eventPayload = generateEvent(event, conversionLabel, isPageEvent); + + if (!eventPayload) { + return false; + } - sendOrQueueEvent(adWordEvent); + sendOrQueueEvent(eventPayload); return true; } - function getBaseAdWordEvent() { - var adWordEvent = {}; - adWordEvent.google_conversion_value = 0; - adWordEvent.google_conversion_language = 'en'; - adWordEvent.google_conversion_format = '3'; - adWordEvent.google_conversion_color = 'ffffff'; - adWordEvent.google_remarketing_only = forwarderSettings.remarketingOnly == 'True'; - adWordEvent.google_conversion_id = forwarderSettings.conversionId; - return adWordEvent; - } + // Looks up an Event's conversionLabel from customAttributeMappings based on computed jsHash value function getConversionLabel(event, isPageEvent) { var jsHash = calculateJSHash(event.EventDataType, event.EventCategory, event.EventName); var type = isPageEvent ? 'EventClass.Id' : 'EventClassDetails.Id'; @@ -156,6 +250,7 @@ return conversionLabel; } + // Filters Event.EventAttributes for attributes that are in customAttributeMappings function getCustomProps(event, isPageEvent) { var customProps = {}; var attributes = event.EventAttributes; @@ -200,27 +295,58 @@ return mParticle.generateHash(preHash); } + function loadGtagSnippet() { + (function () { + window.dataLayer = window.dataLayer || []; + window.gtag = function(){dataLayer.push(arguments);} + + var gTagScript = document.createElement('script'); + gTagScript.async = true; + gTagScript.onload = function () { + gtag('js', new Date()); + gtag('config', gtagSiteId); + }; + gTagScript.src = 'https://www.googletagmanager.com/gtag/js?id=' + gtagSiteId; + document.getElementsByTagName('head')[0].appendChild(gTagScript); + })(); + } + + function loadLegacySnippet() { + (function () { + var googleAdwords = document.createElement('script'); + googleAdwords.type = 'text/javascript'; + googleAdwords.async = true; + googleAdwords.onload = function() { + if (eventQueue.length) { + eventQueue.forEach(function(adWordEvent) { + window.google_trackConversion(adWordEvent); + }); + eventQueue = []; + } + }; + googleAdwords.src = ('https:' == document.location.protocol ? 'https' : 'http') + '://www.googleadservices.com/pagead/conversion_async.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(googleAdwords, s); + })(); + } + function initForwarder(settings, service, testMode) { + forwarderSettings = settings; reportingService = service; try { + if (!forwarderSettings.conversionId) { + return 'Can\'t initialize forwarder: ' + name + ', conversionId is not defined'; + } + + gtagSiteId = "AW-" + forwarderSettings.conversionId; + if (testMode !== true) { - (function () { - var googleAdwords = document.createElement('script'); - googleAdwords.type = 'text/javascript'; - googleAdwords.async = true; - googleAdwords.onload = function() { - if (eventQueue.length) { - eventQueue.forEach(function(adWordEvent) { - window.google_trackConversion(adWordEvent); - }); - eventQueue = []; - } - }; - googleAdwords.src = ('https:' == document.location.protocol ? 'https' : 'http') + '://www.googleadservices.com/pagead/conversion_async.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(googleAdwords, s); - })(); + if (forwarderSettings.enableGtag == 'True') { + loadGtagSnippet(); + } else { + loadLegacySnippet(); + } } if (!forwarderSettings.conversionId) { diff --git a/test/tests.js b/test/tests.js index 07f977e..38887d9 100644 --- a/test/tests.js +++ b/test/tests.js @@ -115,232 +115,543 @@ describe('Adwords forwarder', function () { window.google_track_data.should.have.property("google_conversion_id", 'AW-123123123') } - describe("Page View Conversion Label", function () { - before(function () { + describe('Legacy Conversion Async', function () { + describe("Page View Conversion Label", function () { + before(function () { - var map = [{ "maptype": "EventClassDetails.Id", "value": "pageViewLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'Homepage') }] + var map = [{ "maptype": "EventClassDetails.Id", "value": "pageViewLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'Homepage') }] - mParticle.forwarder.init({ - labels: JSON.stringify(map), - conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + mParticle.forwarder.init({ + labels: JSON.stringify(map), + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); + }); + + + it('should have conversion labels for page view', function (done) { + var successMessage = mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageType.PageView, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock' + } + }); + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + checkCommonProperties(); + window.google_track_data.should.have.property('google_conversion_label', "pageViewLabel123"); + + done(); + }); }); + describe("Page Event Conversion Label", function () { + before(function () { + + var map = [{ "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }] - it('should have conversion labels for page view', function (done) { - var successMessage = mParticle.forwarder.process({ - EventName: 'Homepage', - EventDataType: MessageType.PageView, - EventAttributes: { - showcase: 'something', - test: 'thisoneshouldgetmapped', - mp: 'rock' - } + mParticle.forwarder.init({ + labels: JSON.stringify(map), + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); }); - successMessage.should.not.be.null(); - successMessage.should.be.equal("Successfully sent to GoogleAdWords") - checkCommonProperties(); - window.google_track_data.should.have.property('google_conversion_label', "pageViewLabel123"); - done(); - }); - }); + it('should have conversion labels for page event', function (done) { - describe("Page Event Conversion Label", function () { - before(function () { + var successMessage = mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageType.PageEvent, + EventCategory: EventType.Navigation, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock' + } + }); - var map = [{ "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }] + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + checkCommonProperties(); + window.google_track_data.should.have.property('google_conversion_label', "pageEventLabel123"); - mParticle.forwarder.init({ - labels: JSON.stringify(map), - conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + done(); + }); }); - it('should have conversion labels for page event', function (done) { + describe("Commerce Event Conversion Label", function () { + before(function () { - var successMessage = mParticle.forwarder.process({ - EventName: 'Homepage', - EventDataType: MessageType.PageEvent, - EventCategory: EventType.Navigation, - EventAttributes: { - showcase: 'something', - test: 'thisoneshouldgetmapped', - mp: 'rock' - } + var map = [{ "maptype": "EventClassDetails.Id", "value": "commerceLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + "eCommerce - Purchase") }] + + mParticle.forwarder.init({ + labels: JSON.stringify(map), + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); + }); + + it('should have conversion labels for commerce event', function (done) { + var successMessage = mParticle.forwarder.process({ + EventName: "eCommerce - Purchase", + EventDataType: MessageType.Commerce, + ProductAction: { + ProductActionType: ProductActionType.Purchase, + ProductList: [ + { + Sku: '12345', + Name: 'iPhone 6', + Category: 'Phones', + Brand: 'iPhone', + Variant: '6', + Price: 400, + CouponCode: null, + Quantity: 1 + } + ], + TransactionId: 123, + Affiliation: 'my-affiliation', + TotalAmount: 450, + TaxAmount: 40, + ShippingAmount: 10, + }, + CurrencyCode: "USD" + }); + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + checkCommonProperties(); + window.google_track_data.should.have.property('google_conversion_label', "commerceLabel123"); + + done(); }); + }) + + describe("Custom Parameters", function () { + before(function () { + + var labels = [{ "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }] + var attr = [{ "maptype": "EventAttributeClass.Id", "value": "mycustomprop", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'attributekey') }] - successMessage.should.not.be.null(); - successMessage.should.be.equal("Successfully sent to GoogleAdWords") - checkCommonProperties(); - window.google_track_data.should.have.property('google_conversion_label', "pageEventLabel123"); + mParticle.forwarder.init({ + labels: JSON.stringify(labels), + customParameters: JSON.stringify(attr), + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); + }); - done(); + it('should have custom params for page event', function (done) { + + var successMessage = mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageType.PageEvent, + EventCategory: EventType.Navigation, + EventAttributes: { + attributekey: 'attributevalue' + } + }); + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + checkCommonProperties(); + window.google_track_data.should.have.property('google_custom_params'); + Object.keys(window.google_track_data.google_custom_params).length.should.be.equal(1); + window.google_track_data.google_custom_params.should.have.property('mycustomprop', 'attributevalue') + done(); + }); }); - }); + describe("Unmapped conversion labels", function () { + before(function () { - describe("Commerce Event Conversion Label", function () { - before(function () { + var map = [{ "maptype": "EventClassDetails.Id", "value": "commerceLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + "eCommerce - Purchase") }] - var map = [{ "maptype": "EventClassDetails.Id", "value": "commerceLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + "eCommerce - Purchase") }] + mParticle.forwarder.init({ + labels: JSON.stringify(map), + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); + }); - mParticle.forwarder.init({ - labels: JSON.stringify(map), - conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + it('should not forward unmapped events', function (done) { + var failMessage = mParticle.forwarder.process({ + EventName: 'Something random', + EventDataType: MessageType.Commerce, + EventAttributes: { + showcase: 'something' + } + }); + + failMessage.should.not.be.null(); + failMessage.should.be.containEql("Can't send to forwarder") + done(); + }); }); - it('should have conversion labels for commerce event', function (done) { - var successMessage = mParticle.forwarder.process({ - EventName: "eCommerce - Purchase", - EventDataType: MessageType.Commerce, - ProductAction: { - ProductActionType: ProductActionType.Purchase, - ProductList: [ - { - Sku: '12345', - Name: 'iPhone 6', - Category: 'Phones', - Brand: 'iPhone', - Variant: '6', - Price: 400, - CouponCode: null, - Quantity: 1 - } - ], - TransactionId: 123, - Affiliation: 'my-affiliation', - TotalAmount: 450, - TaxAmount: 40, - ShippingAmount: 10, - }, - CurrencyCode: "USD" + + describe("Bad Label Json", function () { + before(function () { + // The ids are calculated based on the events used in the tests below so they must match exactly. + mParticle.forwarder.init({ + labels: 'baaaaaddddddd json', + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); }); - successMessage.should.not.be.null(); - successMessage.should.be.equal("Successfully sent to GoogleAdWords") - checkCommonProperties(); - window.google_track_data.should.have.property('google_conversion_label', "commerceLabel123"); - done(); + it('should not forward with bad labels json', function (done) { + + var failMessage = mParticle.forwarder.process({ + EventName: 'Something random', + EventDataType: MessageType.Commerce, + EventAttributes: { + showcase: 'something' + } + }); + + failMessage.should.not.be.null(); + failMessage.should.be.containEql("Can't send to forwarder") + done(); + }); }); - }) - describe("Custom Parameters", function () { - before(function () { - var labels = [{ "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }] - var attr = [{ "maptype": "EventAttributeClass.Id", "value": "mycustomprop", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'attributekey') }] + describe("Bad Custom Parameters Json", function () { + before(function () { + // The ids are calculated based on the events used in the tests below so they must match exactly. + mParticle.forwarder.init({ + customParameters: 'sdpfuhasdflasdjfnsdjfsdjfn really baddd json', + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); + }); + + + it('should not forward with bad custom parameters json', function (done) { + + var failMessage = mParticle.forwarder.process({ + EventName: 'Something random', + EventDataType: MessageType.Commerce, + EventAttributes: { + showcase: 'something' + } + }); - mParticle.forwarder.init({ - labels: JSON.stringify(labels), - customParameters: JSON.stringify(attr), - conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + failMessage.should.not.be.null(); + failMessage.should.be.containEql("Can't send to forwarder") + done(); + }); }); + }); - it('should have custom params for page event', function (done) { + describe('GTAG Conversions', function () { + describe('Initializing GTAG', function () { + it('should disable gtag and dataLayer by default', function (done) { + var map = [{ "maptype": "EventClassDetails.Id", "value": "pageViewLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'Homepage') }] - var successMessage = mParticle.forwarder.process({ - EventName: 'Homepage', - EventDataType: MessageType.PageEvent, - EventCategory: EventType.Navigation, - EventAttributes: { - attributekey: 'attributevalue' - } + mParticle.forwarder.init({ + labels: JSON.stringify(map), + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); + + (typeof window.gtag === 'undefined').should.be.true(); + (typeof window.dataLayer === 'undefined').should.be.true(); + done(); }); - successMessage.should.not.be.null(); - successMessage.should.be.equal("Successfully sent to GoogleAdWords") - checkCommonProperties(); - window.google_track_data.should.have.property('google_custom_params'); - Object.keys(window.google_track_data.google_custom_params).length.should.be.equal(1); - window.google_track_data.google_custom_params.should.have.property('mycustomprop', 'attributevalue') - done(); + it('should initialize gtag and dataLayer when user opts in', function (done) { + var map = [{ "maptype": "EventClassDetails.Id", "value": "pageViewLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'Homepage') }] + + mParticle.forwarder.init({ + labels: JSON.stringify(map), + enableGtag: 'True', + conversionId: 'AW-123123123' + }, reportService.cb, 1, true); + + window.gtag.should.be.ok(); + window.dataLayer.should.be.ok(); + + done(); + }); }); - }); - describe("Unmapped conversion labels", function () { - before(function () { + describe("Page View Conversion Label", function () { + before(function () { + window.dataLayer = undefined; + + var map = [{ "maptype": "EventClassDetails.Id", "value": "pageViewLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'Homepage') }] + + mParticle.forwarder.init({ + enableGtag: 'True', + labels: JSON.stringify(map), + conversionId: '123123123' + }, reportService.cb, 1, true); + }); + - var map = [{ "maptype": "EventClassDetails.Id", "value": "commerceLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + "eCommerce - Purchase") }] + it('should have conversion labels for page view', function (done) { + var successMessage = mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageType.PageView, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock' + } + }); - mParticle.forwarder.init({ - labels: JSON.stringify(map), - conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + var expectedDataLayer = [ + 'event', + 'conversion', + { + 'send-to': 'AW-123123123/pageViewLabel123' + } + ]; + + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + window.dataLayer.should.match([expectedDataLayer]); + + done(); + }); }); + describe("Page Event Conversion Label", function () { + before(function () { + window.dataLayer = undefined; + + var map = [{ "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }] - it('should not forward unmapped events', function (done) { - var failMessage = mParticle.forwarder.process({ - EventName: 'Something random', - EventDataType: MessageType.Commerce, - EventAttributes: { - showcase: 'something' - } + mParticle.forwarder.init({ + enableGtag: 'True', + labels: JSON.stringify(map), + conversionId: '123123123' + }, reportService.cb, 1, true); }); - failMessage.should.not.be.null(); - failMessage.should.be.containEql("Can't send to forwarder") - done(); + + it('should have conversion labels for page event', function (done) { + + var successMessage = mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageType.PageEvent, + EventCategory: EventType.Navigation, + EventAttributes: { + showcase: 'something', + test: 'thisoneshouldgetmapped', + mp: 'rock' + } + }); + + var expectedDataLayer = [ + 'event', + 'conversion', + { + 'send-to': 'AW-123123123/pageEventLabel123' + } + ]; + + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + window.dataLayer.should.match([expectedDataLayer]); + + done(); + }); }); - }); - describe("Bad Label Json", function () { - before(function () { - // The ids are calculated based on the events used in the tests below so they must match exactly. - mParticle.forwarder.init({ - labels: 'baaaaaddddddd json', - conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + describe("Commerce Event Conversion Label", function () { + before(function () { + window.dataLayer = undefined; + + var map = [{ "maptype": "EventClassDetails.Id", "value": "commerceLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + "eCommerce - Purchase") }] + + mParticle.forwarder.init({ + enableGtag: 'True', + labels: JSON.stringify(map), + conversionId: '123123123' + }, reportService.cb, 1, true); + }); + + it('should have conversion labels for commerce event', function (done) { + var successMessage = mParticle.forwarder.process({ + EventName: "eCommerce - Purchase", + EventDataType: MessageType.Commerce, + ProductAction: { + ProductActionType: ProductActionType.Purchase, + ProductList: [ + { + Sku: '12345', + Name: 'iPhone 6', + Category: 'Phones', + Brand: 'iPhone', + Variant: '6', + Price: 400, + CouponCode: null, + Quantity: 1 + } + ], + TransactionId: 123, + Affiliation: 'my-affiliation', + TotalAmount: 450, + TaxAmount: 40, + ShippingAmount: 10, + }, + CurrencyCode: "USD" + }); + + var expectedDataLayer = [ + 'event', + 'conversion', + { + 'send-to': 'AW-123123123/commerceLabel123', + order_id: 123, + value: 450, + currency: 'USD', + } + ]; + + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + window.dataLayer.should.match([expectedDataLayer]); + + done(); + }); + }) + + describe("Custom Parameters", function () { + before(function () { + window.dataLayer = undefined; + + var labels = [{ "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }] + var attr = [{ "maptype": "EventAttributeClass.Id", "value": "mycustomprop", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'attributekey') }] + + mParticle.forwarder.init({ + enableGtag: 'True', + labels: JSON.stringify(labels), + customParameters: JSON.stringify(attr), + conversionId: '123123123' + }, reportService.cb, 1, true); + }); + + it('should have custom params for page event', function (done) { + + var successMessage = mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageType.PageEvent, + EventCategory: EventType.Navigation, + EventAttributes: { + attributekey: 'attributevalue' + } + }); + + var expectedDataLayer = [ + 'event', + 'conversion', + { + 'send-to': 'AW-123123123/pageEventLabel123', + mycustomprop: 'attributevalue' + } + ]; + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + window.dataLayer.should.match([expectedDataLayer]); + + done(); + }); }); + describe("Unmapped conversion labels", function () { + before(function () { + window.dataLayer = undefined; - it('should not forward with bad labels json', function (done) { + var map = [{ "maptype": "EventClassDetails.Id", "value": "commerceLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + "eCommerce - Purchase") }] - var failMessage = mParticle.forwarder.process({ - EventName: 'Something random', - EventDataType: MessageType.Commerce, - EventAttributes: { - showcase: 'something' - } + mParticle.forwarder.init({ + enableGtag: 'True', + labels: JSON.stringify(map), + conversionId: '123123123' + }, reportService.cb, 1, true); }); - failMessage.should.not.be.null(); - failMessage.should.be.containEql("Can't send to forwarder") - done(); + it('should not forward unmapped events', function (done) { + var failMessage = mParticle.forwarder.process({ + EventName: 'Something random', + EventDataType: MessageType.Commerce, + EventAttributes: { + showcase: 'something' + } + }); + + failMessage.should.not.be.null(); + failMessage.should.be.containEql("Can't send to forwarder") + done(); + }); }); - }); - describe("Bad Custom Parameters Json", function () { - before(function () { - // The ids are calculated based on the events used in the tests below so they must match exactly. - mParticle.forwarder.init({ - customParameters: 'sdpfuhasdflasdjfnsdjfsdjfn really baddd json', - conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + describe("Bad Label Json", function () { + before(function () { + window.dataLayer = undefined; + + // The ids are calculated based on the events used in the tests below so they must match exactly. + mParticle.forwarder.init({ + enableGtag: 'True', + labels: 'baaaaaddddddd json', + conversionId: '123123123' + }, reportService.cb, 1, true); + }); + + + it('should not forward with bad labels json', function (done) { + + var failMessage = mParticle.forwarder.process({ + EventName: 'Something random', + EventDataType: MessageType.Commerce, + EventAttributes: { + showcase: 'something' + } + }); + + failMessage.should.not.be.null(); + failMessage.should.be.containEql("Can't send to forwarder") + done(); + }); }); - it('should not forward with bad custom parameters json', function (done) { + describe("Bad Custom Parameters Json", function () { + before(function () { + window.dataLayer = undefined; - var failMessage = mParticle.forwarder.process({ - EventName: 'Something random', - EventDataType: MessageType.Commerce, - EventAttributes: { - showcase: 'something' - } + // The ids are calculated based on the events used in the tests below so they must match exactly. + mParticle.forwarder.init({ + enableGtag: 'True', + customParameters: 'sdpfuhasdflasdjfnsdjfsdjfn really baddd json', + conversionId: '123123123' + }, reportService.cb, 1, true); }); - failMessage.should.not.be.null(); - failMessage.should.be.containEql("Can't send to forwarder") - done(); + + it('should not forward with bad custom parameters json', function (done) { + + var failMessage = mParticle.forwarder.process({ + EventName: 'Something random', + EventDataType: MessageType.Commerce, + EventAttributes: { + showcase: 'something' + } + }); + + failMessage.should.not.be.null(); + failMessage.should.be.containEql("Can't send to forwarder") + done(); + }); }); - }); + }); }); From 9fb5751c2c898797dda8cd6e334bc640c6522ce3 Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Mon, 4 Oct 2021 09:57:46 -0400 Subject: [PATCH 3/7] Refactor and address code review comments --- src/GoogleAdWordsEventForwarder.js | 186 +++++++++++++++++------------ test/tests.js | 178 +++++++++++++++++++++++++-- 2 files changed, 276 insertions(+), 88 deletions(-) diff --git a/src/GoogleAdWordsEventForwarder.js b/src/GoogleAdWordsEventForwarder.js index c79b264..7d1e67d 100644 --- a/src/GoogleAdWordsEventForwarder.js +++ b/src/GoogleAdWordsEventForwarder.js @@ -42,17 +42,46 @@ function processEvent(event) { var reportEvent = false; + var sendEventFunction = function() {}; + var generateEventFunction = function () {}; + var conversionLabel; + var eventPayload; if (isInitialized) { + // First, process anything in the queue + processQueue(eventQueue); + try { - if (event.EventDataType == MessageType.PageView) { - reportEvent = logPageEvent(event, false); + if (window.gtag && forwarderSettings.enableGtag == 'True') { + sendEventFunction = sendGtagEvent; + generateEventFunction = generateGtagEvent; + generateCommerceEvent = generateGtagCommerceEvent; + } else if (window.google_trackConversion) { + sendEventFunction = sendLegacyEvent; + generateEventFunction = generateAdwordsEvent; + generateCommerceEvent = generateAdwordsCommerceEvent; + } else { + eventQueue.push({ + action: processEvent, + data: event + }) + + return 'Can\'t send to forwarder ' + name + ', not initialized. Event added to queue.'; } - else if (event.EventDataType == MessageType.PageEvent) { - reportEvent = logPageEvent(event, true); + + // Get conversionLabel to be used for event generation + var conversionLabel = getConversionLabel(event); + var customProps = getCustomProps(event); + + // Determines the proper event to fire + if (event.EventDataType == MessageType.PageView || event.EventDataType == MessageType.PageEvent) { + eventPayload = generateEventFunction(event, conversionLabel, customProps); + } else if (event.EventDataType == MessageType.Commerce && event.ProductAction) { + eventPayload = generateCommerceEvent(event, conversionLabel, customProps); } - else if (event.EventDataType == MessageType.Commerce) { - reportEvent = logCommerce(event); + + if (eventPayload) { + reportEvent = sendEventFunction(eventPayload); } if (reportEvent && reportingService) { @@ -64,11 +93,17 @@ return 'Can\'t send to forwarder: ' + name + '. Event not mapped'; } catch (e) { + console.error('Can\t send to forwarder', e); return 'Can\'t send to forwarder: ' + name + ' ' + e; } + } else { + eventQueue.push({ + action: processEvent, + data: event + }) } - return 'Can\'t send to forwarder ' + name + ', not initialized'; + return 'Can\'t send to forwarder ' + name + ', not initialized. Event added to queue.'; } // Converts an mParticle Event into either Legacy or gtag Event @@ -117,15 +152,15 @@ return adWordEvent; } - function generateAdwordsEvent(mPEvent, conversionLabel, isPageEvent) { + function generateAdwordsEvent(mPEvent, conversionLabel, customProps) { var adWordEvent = getBaseAdWordEvent(); adWordEvent.google_conversion_label = conversionLabel; - adWordEvent.google_custom_params = getCustomProps(mPEvent, isPageEvent); + adWordEvent.google_custom_params = customProps; return adWordEvent; } - function generateAdwordsCommerceEvent(mPEvent, conversionLabel, isPageEvent) { + function generateAdwordsCommerceEvent(mPEvent, conversionLabel, customProps) { var adWordEvent = getBaseAdWordEvent(); adWordEvent.google_conversion_label = conversionLabel; @@ -142,24 +177,20 @@ adWordEvent.google_conversion_value = mPEvent.ProductAction.TotalAmount; } - adWordEvent.google_custom_params = getCustomProps(mPEvent, isPageEvent); + adWordEvent.google_custom_params = customProps; return adWordEvent; } // gtag Events - function generateGtagEvent(mPEvent, conversionLabel, isPageEvent) { + function generateGtagEvent(mPEvent, conversionLabel, customProps) { var conversionPayload = { 'send-to': gtagSiteId + '/' + conversionLabel }; - var customProps = getCustomProps(mPEvent, isPageEvent); - - var payload = Object.assign({}, conversionPayload, customProps); - - return payload; + return mergeObjects(conversionPayload, customProps); } - function generateGtagCommerceEvent(mPEvent, conversionLabel, isPageEvent) { + function generateGtagCommerceEvent(mPEvent, conversionLabel, customProps) { var conversionPayload = { 'send-to': gtagSiteId + '/' + conversionLabel }; @@ -168,10 +199,6 @@ if (mPEvent.ProductAction.ProductActionType === mParticle.ProductActionType.Purchase && mPEvent.ProductAction.TransactionId) { - if (event.ProductAction.ProductActionType === mParticle.ProductActionType.Purchase - && event.ProductAction.TransactionId) { - adWordEvent.google_conversion_order_id = event.ProductAction.TransactionId; - } conversionPayload.order_id = mPEvent.ProductAction.TransactionId; } @@ -183,63 +210,33 @@ conversionPayload.value = mPEvent.ProductAction.TotalAmount; } - var payload = Object.assign({}, conversionPayload, customProps); - - return payload; - } - - // Sends final event to Google or queues if Google isn't ready - function sendOrQueueEvent(conversionPayload) { - if (window.gtag && forwarderSettings.enableGtag == 'True') { - gtag('event', 'conversion', conversionPayload); - } else if (window.google_trackConversion) { - window.google_trackConversion(conversionPayload); - } else { - eventQueue.push(conversionPayload); - } + return mergeObjects(conversionPayload, customProps); } - function logCommerce(event, isPageEvent) { - var isPageEvent = false; - var conversionLabel = getConversionLabel(event, isPageEvent); - - if (typeof (conversionLabel) !== 'string') { - return false; - } - - var eventPayload = generateCommerceEvent(event, conversionLabel, isPageEvent); - - if (!eventPayload) { + function sendGtagEvent(payload) { + try { + gtag('event', 'conversion', payload); + } catch (e) { + console.error('gtag is not available to send payload', payload); return false; } - - sendOrQueueEvent(eventPayload); - return true; } - function logPageEvent(event, isPageEvent) { - var conversionLabel = getConversionLabel(event, isPageEvent); - if (typeof (conversionLabel) != 'string') { - return false; - } - - var eventPayload = generateEvent(event, conversionLabel, isPageEvent); - - if (!eventPayload) { + function sendLegacyEvent(payload) { + try { + window.google_trackConversion(payload); + } catch (e) { + console.error('google_trackConversion is not available to send payload', payload); return false; } - - sendOrQueueEvent(eventPayload); - return true; } - // Looks up an Event's conversionLabel from customAttributeMappings based on computed jsHash value - function getConversionLabel(event, isPageEvent) { + function getConversionLabel(event) { var jsHash = calculateJSHash(event.EventDataType, event.EventCategory, event.EventName); - var type = isPageEvent ? 'EventClass.Id' : 'EventClassDetails.Id'; + var type = event.EventDataType === MessageType.PageEvent ? 'EventClass.Id' : 'EventClassDetails.Id'; var conversionLabel = null; var mappingEntry = findValueInMapping(jsHash, type, labels); @@ -251,10 +248,10 @@ } // Filters Event.EventAttributes for attributes that are in customAttributeMappings - function getCustomProps(event, isPageEvent) { + function getCustomProps(event) { var customProps = {}; var attributes = event.EventAttributes; - var type = isPageEvent ? 'EventAttributeClass.Id' : 'EventAttributeClassDetails.Id'; + var type = event.EventDataType === MessageType.PageEvent ? 'EventAttributeClass.Id' : 'EventAttributeClassDetails.Id'; if (attributes) { for (var attributeKey in attributes) { @@ -305,6 +302,8 @@ gTagScript.onload = function () { gtag('js', new Date()); gtag('config', gtagSiteId); + isInitialized = true; + processQueue(eventQueue); }; gTagScript.src = 'https://www.googletagmanager.com/gtag/js?id=' + gtagSiteId; document.getElementsByTagName('head')[0].appendChild(gTagScript); @@ -317,15 +316,11 @@ googleAdwords.type = 'text/javascript'; googleAdwords.async = true; googleAdwords.onload = function() { - if (eventQueue.length) { - eventQueue.forEach(function(adWordEvent) { - window.google_trackConversion(adWordEvent); - }); - eventQueue = []; - } + isInitialized = true; + processQueue(eventQueue); }; googleAdwords.src = ('https:' == document.location.protocol ? 'https' : 'http') + '://www.googleadservices.com/pagead/conversion_async.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(googleAdwords, s); + document.getElementsByTagName('head')[0].appendChild(googleAdwords); })(); } @@ -347,6 +342,9 @@ } else { loadLegacySnippet(); } + } else { + isInitialized = true; + processQueue(eventQueue); } if (!forwarderSettings.conversionId) { @@ -367,8 +365,6 @@ return 'Can\'t initialize forwarder: ' + name + ', Could not process event to label mapping'; } - isInitialized = true; - return 'Successfully initialized: ' + name; } catch (e) { @@ -376,8 +372,34 @@ } } + function processQueue(queue) { + var item; + + if ((window.gtag || window.google_trackConversion) && queue.length > 0) { + try { + while (queue.length > 0) { + item = queue.shift() + item.action(item.data); + } + } catch (e) { + console.error('Error on mParticle Adwords Kit', e); + } + } + } + + function purgeQueue(queue) { + if (queue.length) { + queue.forEach(function (action, data) { + action(data); + }); + queue = []; + } + } + this.init = initForwarder; this.process = processEvent; + this.purgeQueue = purgeQueue; + this.processQueue = processQueue; }; function getId() { @@ -412,6 +434,18 @@ return val != null && typeof val === 'object' && Array.isArray(val) === false; } + function mergeObjects() { + var resObj = {}; + for(var i=0; i < arguments.length; i += 1) { + var obj = arguments[i], + keys = Object.keys(obj); + for(var j=0; j < keys.length; j += 1) { + resObj[keys[j]] = obj[keys[j]]; + } + } + return resObj; + } + if (typeof window !== 'undefined') { if (window && window.mParticle && window.mParticle.addForwarder) { window.mParticle.addForwarder({ diff --git a/test/tests.js b/test/tests.js index 38887d9..b372cb7 100644 --- a/test/tests.js +++ b/test/tests.js @@ -124,7 +124,7 @@ describe('Adwords forwarder', function () { mParticle.forwarder.init({ labels: JSON.stringify(map), conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + }, reportService.cb, true, true); }); @@ -156,7 +156,7 @@ describe('Adwords forwarder', function () { mParticle.forwarder.init({ labels: JSON.stringify(map), conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + }, reportService.cb, true, true); }); @@ -191,7 +191,7 @@ describe('Adwords forwarder', function () { mParticle.forwarder.init({ labels: JSON.stringify(map), conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + }, reportService.cb, true, true); }); it('should have conversion labels for commerce event', function (done) { @@ -233,14 +233,22 @@ describe('Adwords forwarder', function () { describe("Custom Parameters", function () { before(function () { - var labels = [{ "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }] - var attr = [{ "maptype": "EventAttributeClass.Id", "value": "mycustomprop", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'attributekey') }] + var labels = [ + { "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }, + { "maptype": "EventClassDetails.Id", "value": "pageViewLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'Homepage') }, + { "maptype": "EventClassDetails.Id", "value": "commerceLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + "eCommerce - Purchase") }, + ]; + var attr = [ + { "maptype": "EventAttributeClass.Id", "value": "mycustomprop", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'attributekey') }, + { "maptype": "EventAttributeClassDetails.Id", "value": "title", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'title') }, + { "maptype": "EventAttributeClassDetails.Id", "value": "sale", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + 'sale') } + ]; mParticle.forwarder.init({ labels: JSON.stringify(labels), customParameters: JSON.stringify(attr), conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + }, reportService.cb, true, true); }); it('should have custom params for page event', function (done) { @@ -262,6 +270,65 @@ describe('Adwords forwarder', function () { window.google_track_data.google_custom_params.should.have.property('mycustomprop', 'attributevalue') done(); }); + + it('should have custom params for page view', function (done) { + + var successMessage = mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageType.PageView, + EventAttributes: { + title: 'my page view' + } + }); + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + checkCommonProperties(); + window.google_track_data.should.have.property('google_custom_params'); + Object.keys(window.google_track_data.google_custom_params).length.should.be.equal(1); + window.google_track_data.google_custom_params.should.have.property('title', 'my page view'); + done(); + }); + + it('should have custom params for commerce events', function (done) { + + var successMessage = mParticle.forwarder.process({ + EventName: "eCommerce - Purchase", + EventDataType: MessageType.Commerce, + EventAttributes: { + sale: 'seasonal sale' + }, + ProductAction: { + ProductActionType: ProductActionType.Purchase, + ProductList: [ + { + Sku: '12345', + Name: 'iPhone 6', + Category: 'Phones', + Brand: 'iPhone', + Variant: '6', + Price: 400, + CouponCode: null, + Quantity: 1 + } + ], + TransactionId: 123, + Affiliation: 'my-affiliation', + TotalAmount: 450, + TaxAmount: 40, + ShippingAmount: 10, + }, + CurrencyCode: "USD" + }); + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + checkCommonProperties(); + window.google_track_data.should.have.property('google_custom_params'); + Object.keys(window.google_track_data.google_custom_params).length.should.be.equal(1); + window.google_track_data.google_custom_params.should.have.property('sale', 'seasonal sale'); + done(); + }); }); describe("Unmapped conversion labels", function () { @@ -272,7 +339,7 @@ describe('Adwords forwarder', function () { mParticle.forwarder.init({ labels: JSON.stringify(map), conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + }, reportService.cb, true, true); }); it('should not forward unmapped events', function (done) { @@ -297,7 +364,7 @@ describe('Adwords forwarder', function () { mParticle.forwarder.init({ labels: 'baaaaaddddddd json', conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + }, reportService.cb, true, true); }); @@ -324,7 +391,7 @@ describe('Adwords forwarder', function () { mParticle.forwarder.init({ customParameters: 'sdpfuhasdflasdjfnsdjfsdjfn really baddd json', conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + }, reportService.cb, true, true); }); @@ -353,7 +420,7 @@ describe('Adwords forwarder', function () { mParticle.forwarder.init({ labels: JSON.stringify(map), conversionId: 'AW-123123123' - }, reportService.cb, 1, true); + }, reportService.cb, true, true); (typeof window.gtag === 'undefined').should.be.true(); (typeof window.dataLayer === 'undefined').should.be.true(); @@ -417,6 +484,7 @@ describe('Adwords forwarder', function () { done(); }); }); + describe("Page Event Conversion Label", function () { before(function () { window.dataLayer = undefined; @@ -526,8 +594,16 @@ describe('Adwords forwarder', function () { before(function () { window.dataLayer = undefined; - var labels = [{ "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }] - var attr = [{ "maptype": "EventAttributeClass.Id", "value": "mycustomprop", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'attributekey') }] + var labels = [ + { "maptype": "EventClass.Id", "value": "pageEventLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'Homepage') }, + { "maptype": "EventClassDetails.Id", "value": "pageViewLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'Homepage') }, + { "maptype": "EventClassDetails.Id", "value": "commerceLabel123", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + "eCommerce - Purchase") }, + ]; + var attr = [ + { "maptype": "EventAttributeClass.Id", "value": "mycustomprop", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageEvent + "" + EventType.Navigation + 'attributekey') }, + { "maptype": "EventAttributeClassDetails.Id", "value": "title", "map": "0", "jsmap": mParticle.generateHash(MessageType.PageView + "" + 'title') }, + { "maptype": "EventAttributeClassDetails.Id", "value": "sale", "map": "0", "jsmap": mParticle.generateHash(MessageType.Commerce + "" + 'sale') } + ] mParticle.forwarder.init({ enableGtag: 'True', @@ -563,6 +639,84 @@ describe('Adwords forwarder', function () { done(); }); + + it('should have custom params for page view', function (done) { + + + var successMessage = mParticle.forwarder.process({ + EventName: 'Homepage', + EventDataType: MessageType.PageView, + EventAttributes: { + title: 'my page title' + } + }); + + var expectedDataLayer = [ + 'event', + 'conversion', + { + 'send-to': 'AW-123123123/pageViewLabel123', + action: 'code', + title: 'my page title' + } + ]; + + // debugger; + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + window.dataLayer.should.matchAny(expectedDataLayer); + + done(); + }); + + it('should have custom params for commerce event', function (done) { + + var successMessage = mParticle.forwarder.process({ + EventName: "eCommerce - Purchase", + EventDataType: MessageType.Commerce, + EventAttributes: { + sale: 'seasonal sale' + }, + ProductAction: { + ProductActionType: ProductActionType.Purchase, + ProductList: [ + { + Sku: '12345', + Name: 'iPhone 6', + Category: 'Phones', + Brand: 'iPhone', + Variant: '6', + Price: 400, + CouponCode: null, + Quantity: 1 + } + ], + TransactionId: 123, + Affiliation: 'my-affiliation', + TotalAmount: 450, + TaxAmount: 40, + ShippingAmount: 10, + }, + CurrencyCode: "USD" + }); + + var expectedDataLayer = [ + 'event', + 'conversion', + { + 'send-to': 'AW-123123123/commerceLabel123', + action: 'code', + sale: 'seasonal sale' + } + ]; + + successMessage.should.not.be.null(); + successMessage.should.be.equal("Successfully sent to GoogleAdWords") + window.dataLayer.should.matchAny(expectedDataLayer); + + done(); + }); }); describe("Unmapped conversion labels", function () { From dc068f0b65f7c57b568805f6cfe7565ca3198a0d Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Mon, 4 Oct 2021 11:52:57 -0400 Subject: [PATCH 4/7] Fix broken tests --- test/tests.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/tests.js b/test/tests.js index b372cb7..e87be42 100644 --- a/test/tests.js +++ b/test/tests.js @@ -656,7 +656,6 @@ describe('Adwords forwarder', function () { 'conversion', { 'send-to': 'AW-123123123/pageViewLabel123', - action: 'code', title: 'my page title' } ]; @@ -706,7 +705,6 @@ describe('Adwords forwarder', function () { 'conversion', { 'send-to': 'AW-123123123/commerceLabel123', - action: 'code', sale: 'seasonal sale' } ]; From 98e2899f6dfcee2f7d87ccfc2c61e98e31fa1fb9 Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Mon, 4 Oct 2021 15:27:30 -0400 Subject: [PATCH 5/7] Address Code Review Comments --- src/GoogleAdWordsEventForwarder.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/GoogleAdWordsEventForwarder.js b/src/GoogleAdWordsEventForwarder.js index 7d1e67d..50add39 100644 --- a/src/GoogleAdWordsEventForwarder.js +++ b/src/GoogleAdWordsEventForwarder.js @@ -56,10 +56,13 @@ sendEventFunction = sendGtagEvent; generateEventFunction = generateGtagEvent; generateCommerceEvent = generateGtagCommerceEvent; + } else if (window.google_trackConversion) { - sendEventFunction = sendLegacyEvent; + // window.google_trackConversion is a legacy API and will be deprecated + sendEventFunction = sendAdwordsEvent; generateEventFunction = generateAdwordsEvent; generateCommerceEvent = generateAdwordsCommerceEvent; + } else { eventQueue.push({ action: processEvent, @@ -217,17 +220,17 @@ try { gtag('event', 'conversion', payload); } catch (e) { - console.error('gtag is not available to send payload', payload); + console.error('gtag is not available to send payload: ', payload, e); return false; } return true; } - function sendLegacyEvent(payload) { + function sendAdwordsEvent(payload) { try { window.google_trackConversion(payload); } catch (e) { - console.error('google_trackConversion is not available to send payload', payload); + console.error('google_trackConversion is not available to send payload: ', payload, e); return false; } return true; @@ -387,18 +390,8 @@ } } - function purgeQueue(queue) { - if (queue.length) { - queue.forEach(function (action, data) { - action(data); - }); - queue = []; - } - } - this.init = initForwarder; this.process = processEvent; - this.purgeQueue = purgeQueue; this.processQueue = processQueue; }; From 22e615526052001144830b43b5163c7e654d4b5b Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Fri, 15 Oct 2021 19:37:30 -0400 Subject: [PATCH 6/7] Fix bug where null value is placed in DataLayer --- src/GoogleAdWordsEventForwarder.js | 20 +++++++++++++------- test/tests.js | 10 +++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/GoogleAdWordsEventForwarder.js b/src/GoogleAdWordsEventForwarder.js index 50add39..a67e942 100644 --- a/src/GoogleAdWordsEventForwarder.js +++ b/src/GoogleAdWordsEventForwarder.js @@ -185,20 +185,26 @@ } // gtag Events + function getBaseGtagEvent(conversionLabel) { + return { + 'send-to': gtagSiteId + '/' + conversionLabel, + 'value': 0, + 'language': 'en', + 'remarketing_only': forwarderSettings.remarketingOnly == 'True' + } + } + function generateGtagEvent(mPEvent, conversionLabel, customProps) { - var conversionPayload = { - 'send-to': gtagSiteId + '/' + conversionLabel - }; + if (!conversionLabel) { return }; + var conversionPayload = getBaseGtagEvent(conversionLabel); return mergeObjects(conversionPayload, customProps); } function generateGtagCommerceEvent(mPEvent, conversionLabel, customProps) { - var conversionPayload = { - 'send-to': gtagSiteId + '/' + conversionLabel - }; + if (!conversionLabel) { return }; - var customProps = getCustomProps(mPEvent, isPageEvent); + var conversionPayload = getBaseGtagEvent(conversionLabel); if (mPEvent.ProductAction.ProductActionType === mParticle.ProductActionType.Purchase && mPEvent.ProductAction.TransactionId) { diff --git a/test/tests.js b/test/tests.js index e87be42..e50aa01 100644 --- a/test/tests.js +++ b/test/tests.js @@ -705,7 +705,11 @@ describe('Adwords forwarder', function () { 'conversion', { 'send-to': 'AW-123123123/commerceLabel123', - sale: 'seasonal sale' + currency: 'USD', + language: 'en', + remarketing_only: false, + sale: 'seasonal sale', + value: 450 } ]; @@ -739,8 +743,11 @@ describe('Adwords forwarder', function () { } }); + // debugger; + failMessage.should.not.be.null(); failMessage.should.be.containEql("Can't send to forwarder") + window.dataLayer.length.should.eql(0) done(); }); }); @@ -771,6 +778,7 @@ describe('Adwords forwarder', function () { failMessage.should.not.be.null(); failMessage.should.be.containEql("Can't send to forwarder") + window.dataLayer.length.should.eql(0) done(); }); }); From 0deb460562c44fdcb4719aa7a52ab2ac4240fcc7 Mon Sep 17 00:00:00 2001 From: Alexander Sapountzis Date: Mon, 18 Oct 2021 15:37:05 -0400 Subject: [PATCH 7/7] Address Code Review Comments and remove unused code --- src/GoogleAdWordsEventForwarder.js | 16 ++-------------- test/tests.js | 2 -- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/GoogleAdWordsEventForwarder.js b/src/GoogleAdWordsEventForwarder.js index a67e942..114918e 100644 --- a/src/GoogleAdWordsEventForwarder.js +++ b/src/GoogleAdWordsEventForwarder.js @@ -109,18 +109,6 @@ return 'Can\'t send to forwarder ' + name + ', not initialized. Event added to queue.'; } - // Converts an mParticle Event into either Legacy or gtag Event - function generateEvent(mPEvent, conversionLabel, isPageEvent) { - if (window.gtag && forwarderSettings.enableGtag == 'True') { - return generateGtagEvent(mPEvent, conversionLabel, isPageEvent); - } else if (window.google_trackConversion) { - return generateAdwordsEvent(mPEvent, conversionLabel, isPageEvent); - } else { - console.error('Unrecognized Event', mPEvent); - return false; - } - } - // Converts an mParticle Commerce Event into either Legacy or gtag Event function generateCommerceEvent(mPEvent, conversionLabel, isPageEvent) { if (mPEvent.ProductAction @@ -195,14 +183,14 @@ } function generateGtagEvent(mPEvent, conversionLabel, customProps) { - if (!conversionLabel) { return }; + if (!conversionLabel) { return null; }; var conversionPayload = getBaseGtagEvent(conversionLabel); return mergeObjects(conversionPayload, customProps); } function generateGtagCommerceEvent(mPEvent, conversionLabel, customProps) { - if (!conversionLabel) { return }; + if (!conversionLabel) { return null; }; var conversionPayload = getBaseGtagEvent(conversionLabel); diff --git a/test/tests.js b/test/tests.js index e50aa01..be218a0 100644 --- a/test/tests.js +++ b/test/tests.js @@ -660,8 +660,6 @@ describe('Adwords forwarder', function () { } ]; - // debugger; - successMessage.should.not.be.null(); successMessage.should.be.equal("Successfully sent to GoogleAdWords") window.dataLayer.should.matchAny(expectedDataLayer);