From d204877dbe00824f8fb82a615680bf8a388f3e9a Mon Sep 17 00:00:00 2001 From: Dan Wilkerson Date: Sun, 28 Aug 2016 00:47:39 -0400 Subject: [PATCH 1/5] Added transactionTrack --- dist/angulartics-ga.min.js | 2 +- dist/angulartics-ga.min.js.map | 2 +- lib/angulartics-ga.js | 144 ++++++++++++++++++++++++++++++++- 3 files changed, 145 insertions(+), 3 deletions(-) diff --git a/dist/angulartics-ga.min.js b/dist/angulartics-ga.min.js index dc728d0..b24e15b 100644 --- a/dist/angulartics-ga.min.js +++ b/dist/angulartics-ga.min.js @@ -1,2 +1,2 @@ -!function(window,angular,undefined){"use strict";angular.module("angulartics.google.analytics",["angulartics"]).config(["$analyticsProvider",function($analyticsProvider){function dimensionsAndMetrics(properties){if(window.ga){var key,customData={};for(key in properties)key.indexOf("dimension")&&key.indexOf("metric")||(customData[key]=properties[key]);return customData}}function eventTrack(action,properties){if(!$analyticsProvider.settings.ga.disableEventTracking){if(properties&&properties.category||(properties=properties||{},properties.category="Event"),properties.value){var parsed=parseInt(properties.value,10);properties.value=isNaN(parsed)?0:parsed}if(properties.hitCallback&&"function"!=typeof properties.hitCallback&&(properties.hitCallback=null),properties.hasOwnProperty("nonInteraction")||(properties.nonInteraction=properties.noninteraction),window.ga){var eventOptions={eventCategory:properties.category,eventAction:action,eventLabel:properties.label,eventValue:properties.value,nonInteraction:properties.nonInteraction,page:properties.page||window.location.hash.substring(1)||window.location.pathname,userId:$analyticsProvider.settings.ga.userId,hitCallback:properties.hitCallback},dimsAndMets=dimensionsAndMetrics(properties);angular.extend(eventOptions,dimsAndMets),$analyticsProvider.settings.ga.transport&&angular.extend(eventOptions,$analyticsProvider.settings.ga.transport),ga("send","event",eventOptions),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){ga(accountName+".send","event",eventOptions)})}else window._gaq&&_gaq.push(["_trackEvent",properties.category,action,properties.label,properties.value,properties.nonInteraction])}}$analyticsProvider.settings.pageTracking.trackRelativePath=!0,$analyticsProvider.settings.ga={additionalAccountNames:undefined,disableEventTracking:null,disablePageTracking:null,userId:null},$analyticsProvider.registerPageTrack(function(path,properties){if(!$analyticsProvider.settings.ga.disablePageTracking&&(window._gaq&&(_gaq.push(["_trackPageview",path]),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){_gaq.push([accountName+"._trackPageview",path])})),window.ga)){var dimsAndMets=dimensionsAndMetrics(properties);$analyticsProvider.settings.ga.userId&&ga("set","userId",$analyticsProvider.settings.ga.userId),ga("send","pageview",path,dimsAndMets),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){ga(accountName+".send","pageview",path,dimsAndMets)})}}),$analyticsProvider.registerEventTrack(eventTrack),$analyticsProvider.registerExceptionTrack(function(error,cause){eventTrack(error.toString(),{category:"Exceptions",label:error.stack,nonInteraction:!0})}),$analyticsProvider.registerSetUsername(function(userId){$analyticsProvider.settings.ga.userId=userId}),$analyticsProvider.registerSetUserProperties(function(properties){if(properties){var dimsAndMets=dimensionsAndMetrics(properties);ga("set",dimsAndMets)}}),$analyticsProvider.registerUserTimings(function(properties){return properties&&properties.timingCategory&&properties.timingVar&&"undefined"!=typeof properties.timingValue?void(window.ga&&ga("send","timing",properties)):void console.log("Properties timingCategory, timingVar, and timingValue are required to be set.")})}])}(window,window.angular); +!function(window,angular,undefined){"use strict";angular.module("angulartics.google.analytics",["angulartics"]).config(["$analyticsProvider",function($analyticsProvider){function dimensionsAndMetrics(properties){if(window.ga){var key,customData={};for(key in properties)key.indexOf("dimension")&&key.indexOf("metric")||(customData[key]=properties[key]);return customData}}function eventTrack(action,properties){if(!$analyticsProvider.settings.ga.disableEventTracking){if(properties&&properties.category||(properties=properties||{},properties.category="Event"),properties.value){var parsed=parseInt(properties.value,10);properties.value=isNaN(parsed)?0:parsed}if(properties.hitCallback&&"function"!=typeof properties.hitCallback&&(properties.hitCallback=null),properties.hasOwnProperty("nonInteraction")||(properties.nonInteraction=properties.noninteraction),window.ga){var eventOptions={eventCategory:properties.category,eventAction:action,eventLabel:properties.label,eventValue:properties.value,nonInteraction:properties.nonInteraction,page:properties.page||window.location.hash.substring(1)||window.location.pathname,userId:$analyticsProvider.settings.ga.userId,hitCallback:properties.hitCallback},dimsAndMets=dimensionsAndMetrics(properties);angular.extend(eventOptions,dimsAndMets),$analyticsProvider.settings.ga.transport&&angular.extend(eventOptions,$analyticsProvider.settings.ga.transport),ga("send","event",eventOptions),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){ga(accountName+".send","event",eventOptions)})}else window._gaq&&_gaq.push(["_trackEvent",properties.category,action,properties.label,properties.value,properties.nonInteraction])}}$analyticsProvider.settings.pageTracking.trackRelativePath=!0,$analyticsProvider.settings.ga={additionalAccountNames:undefined,disableEventTracking:null,disablePageTracking:null,userId:null,enhancedEcommerce:!1},$analyticsProvider.registerPageTrack(function(path,properties){if(!$analyticsProvider.settings.ga.disablePageTracking&&(window._gaq&&(_gaq.push(["_trackPageview",path]),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){_gaq.push([accountName+"._trackPageview",path])})),window.ga)){var dimsAndMets=dimensionsAndMetrics(properties);$analyticsProvider.settings.ga.userId&&ga("set","userId",$analyticsProvider.settings.ga.userId),ga("send","pageview",path,dimsAndMets),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){ga(accountName+".send","pageview",path,dimsAndMets)})}}),$analyticsProvider.registerEventTrack(eventTrack),$analyticsProvider.registerExceptionTrack(function(error,cause){eventTrack(error.toString(),{category:"Exceptions",label:error.stack,nonInteraction:!0})}),$analyticsProvider.registerSetUsername(function(userId){$analyticsProvider.settings.ga.userId=userId}),$analyticsProvider.registerSetUserProperties(function(properties){if(properties){var dimsAndMets=dimensionsAndMetrics(properties);ga("set",dimsAndMets)}}),$analyticsProvider.registerUserTimings(function(properties){return properties&&properties.timingCategory&&properties.timingVar&&"undefined"!=typeof properties.timingValue?void(window.ga&&ga("send","timing",properties)):void console.log("Properties timingCategory, timingVar, and timingValue are required to be set.")}),$analyticsProvider.registerTransactionTrack(function(transaction){if(!transaction.id&&transaction.id+""!="0")return console.log("Missing required field transaction.id");var product,item,i,isEnhancedEcommerce=$analyticsProvider.settings.ga.enhancedEcommerce,plugin=isEnhancedEcommerce?"ec":"ecommerce",addTransCmd=isEnhancedEcommerce?["ec:setAction","purchase"]:["ecommerce:addTransaction"],addItemCmd=isEnhancedEcommerce?"ec:addProduct":"ecommerce:addItem",dimsAndMets=dimensionsAndMetrics(transaction),purchase={id:transaction.id,affiliation:transaction.affiliation,revenue:transaction.revenue,tax:transaction.tax,shipping:transaction.shipping,coupon:transaction.coupon};for(isEnhancedEcommerce||angular.extend(purchase,dimsAndMets),addTransCmd.push(purchase),ga("require",plugin),ga.apply(this,addTransCmd),i=0;i Date: Sun, 28 Aug 2016 00:49:10 -0400 Subject: [PATCH 2/5] Updated version, added myself to contribs --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 46eb899..85bb03e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angulartics-google-analytics", - "version": "0.2.1", + "version": "0.3.0", "description": "Google Analytics plugin for Angulartics", "keywords": [ "google", @@ -27,7 +27,8 @@ "Blake Miller (https://github.com/blak3mill3r)", "Hannah Fouasnon (https://github.com/fouasnon)", "Alex Logvynovskiy (https://github.com/dr-axly)", - "Fil Maj (https://github.com/filmaj)" + "Fil Maj (https://github.com/filmaj)", + "Dan Wilkerson (https://github.com/danwilkerson)" ], "main": "lib/index.js", "scripts": { From 9bd97e40d425468af7b28a192938d4859c0cbe02 Mon Sep 17 00:00:00 2001 From: Dan Wilkerson Date: Sun, 28 Aug 2016 09:33:40 -0400 Subject: [PATCH 3/5] Added detection functions, added docs --- lib/angulartics-ga.js | 122 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 10 deletions(-) diff --git a/lib/angulartics-ga.js b/lib/angulartics-ga.js index ab631e4..9367e4e 100644 --- a/lib/angulartics-ga.js +++ b/lib/angulartics-ga.js @@ -23,7 +23,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) }; function dimensionsAndMetrics(properties) { - if (window.ga) { + if (detectUniversalAnalytics()) { // add custom dimensions and metrics var customData = {}; var key; @@ -42,13 +42,13 @@ angular.module('angulartics.google.analytics', ['angulartics']) // Do nothing if page tracking is disabled if ($analyticsProvider.settings.ga.disablePageTracking) return; - if (window._gaq) { + if (detectClassicAnalytics()) { _gaq.push(['_trackPageview', path]); angular.forEach($analyticsProvider.settings.ga.additionalAccountNames, function (accountName){ _gaq.push([accountName + '._trackPageview', path]); }); } - if (window.ga) { + if (detectUniversalAnalytics()) { var dimsAndMets = dimensionsAndMetrics(properties); if ($analyticsProvider.settings.ga.userId) { ga('set', 'userId', $analyticsProvider.settings.ga.userId); @@ -103,7 +103,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) properties.nonInteraction = properties.noninteraction; } - if (window.ga) { + if (detectUniversalAnalytics()) { var eventOptions = { eventCategory: properties.category, @@ -131,7 +131,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) ga(accountName +'.send', 'event', eventOptions); }); - } else if (window._gaq) { + } else if (detectClassicAnalytics()) { _gaq.push(['_trackEvent', properties.category, action, properties.label, properties.value, properties.nonInteraction]); } @@ -176,7 +176,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#customs */ $analyticsProvider.registerSetUserProperties(function (properties) { - if(properties) { + if(properties && detectUniversalAnalytics()) { // add custom dimensions and metrics to each hit var dimsAndMets = dimensionsAndMetrics(properties); ga('set', dimsAndMets); @@ -202,7 +202,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) return; } - if(window.ga) { + if(detectUniversalAnalytics()) { ga('send', 'timing', properties); } }); @@ -213,13 +213,17 @@ angular.module('angulartics.google.analytics', ['angulartics']) * * @param {object} transaction comprised of mandatory fields: * 'id': 'T12345', // Transaction ID. Required for purchases and refunds. - * 'affiliation': 'Online Store', // Enhanced Ecommerce only + * 'affiliation': 'Online Store', * 'revenue': '35.43', // Total transaction value (incl. tax and shipping) * 'tax':'4.90', * 'shipping': '5.99', * 'coupon': 'SUMMER_SALE', // Enhanced Ecommerce Only * 'dimension1': 'Card ID #1234', // Hit, Session, or User-level Custom Dimension(s) * 'metric1': 1, // Custom Metric(s) + * 'currencyCode': 'EUR', // Currency Code to track the transaction with. Recognized codes: https://support.google.com/analytics/answer/6205902?hl=en#supported-currencies + * 'city': 'San Francisco', // Classic Analytics only + * 'region': 'California', // Classic Analytics only + * 'country': 'USA', // Classic Analytics only * 'products': [{ // List of products * 'name': 'Triblend Android T-Shirt', // Name or ID is required. * 'id': '12345', // Product SKU @@ -229,6 +233,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) * 'variant': 'Gray', // Enhanced Ecommerce only * 'quantity': 1, * 'coupon': '', // Enhanced Ecommerce only. + * 'currencyCode': 'BRL', // Product-level currency code, Enhanced Ecommerce only * 'dimension2': 'Clearance', // Product-level Custom Dimension * 'metric2': 1 // Product-level Custom Metric * }, @@ -240,7 +245,15 @@ angular.module('angulartics.google.analytics', ['angulartics']) * Utilizes traditional ecommerce tracking by default. To used Enhanced Ecommerce, * set the $analytics.settings.ga.enhancedEcommerce flag to true * - * Docs on traditional + * Docs on traditional ecommerce (UA): + * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce + * + * Docs on Enhanced Ecommerce + * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce + * + * + * Docs on Classic Ecommerce (_gaq) + * @link https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingEcommerce **/ $analyticsProvider.registerTransactionTrack(function(transaction) { @@ -249,6 +262,21 @@ angular.module('angulartics.google.analytics', ['angulartics']) return console.log('Missing required field transaction.id'); } + + if (detectUniversalAnalytics()) { + + return transactionTrackUa(transaction); + + } if else (detectClassicAnalytics()) { + + return transactionTrackClassic(transaction); + + } + + }); + + function transactionTrackUa(transaction) { + // Detect whether the plugin has been configured for traditional or enhanced var isEnhancedEcommerce = $analyticsProvider.settings.ga.enhancedEcommerce; // Dynamically establish the name of the Google Analytics plugin we'll need to require @@ -264,11 +292,13 @@ angular.module('angulartics.google.analytics', ['angulartics']) // set those even if the hit isn't an EE transaction var purchase = { id: transaction.id, - affiliation: transaction.affiliation, // Enhanced Ecommerce only + affiliation: transaction.affiliation, revenue: transaction.revenue, tax: transaction.tax, shipping: transaction.shipping, coupon: transaction.coupon, // Enhanced Ecommerce only + currency: transaction.currency, + currencyCode: transaction.currency }; // Declare some variables for later var product; @@ -311,6 +341,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) item.variant = product.variant; item.coupon = product.coupon; item.id = product.id; + item.currencyCode = product.currencyCode; } else { @@ -318,6 +349,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) // for all products item.id = transaction.id; item.sku = product.id; + item.currency = product.currencyCode; } @@ -348,5 +380,75 @@ angular.module('angulartics.google.analytics', ['angulartics']) }); + function transactionTrackClassic(transaction) { + + // Build our purchase array + var purchase = [ + '_addTrans' + transaction.id, + transaction.affiliation, + transaction.total, + transaction.tax, + transaction.shipping, + transaction.city, + transaction.region, + transaction.country + ]; + var product; + var item; + var i; + + // Issue the command to create a purchase + _gaq.push(purchase); + + // Add in our products + for (i = 0; i < transaction.products.length; i++) { + + product = products[i]; + item = [ + '_addItem', + transaction.id, + product.sku, + product.name, + product.category, + product.price, + product.quantity + ]; + + _gaq.push(item); + + } + + // If a currencyCode is set for the transaction, set it + if (transaction.currencyCode) { + + _gaq.push(['_set', 'currencyCode', transaction.currencyCode]); + + } + + // Issue the command to fire our transaction + _gaq.push(['_trackTrans']); + + } + + function detectUniversalAnalytics() { + + // The Google Analytics snippet stores the name of the GA Global + // in the window property GoogleAnalyticsObject. Detecting UA + // with this parameter accounts for edge cases where the user + // has manually changed this value by adjusting the default + // snippet. + var gaNamespace = window.GoogleAnalyticsObject; + return gaNamespace && window[gaNamespace]; + + } + + function detectClassicAnalytics() { + + // If _gaq is undefined, we're trusting Classic Analytics to be there + return typeof window._gaq !== 'undefined'; + + } + }]); })(window, window.angular); From b097a6ea7c49b7e9042ab9884e5b8fb5f3849080 Mon Sep 17 00:00:00 2001 From: Dan Wilkerson Date: Sun, 28 Aug 2016 09:36:23 -0400 Subject: [PATCH 4/5] Fixing errors --- lib/angulartics-ga.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/angulartics-ga.js b/lib/angulartics-ga.js index 9367e4e..35e4d0c 100644 --- a/lib/angulartics-ga.js +++ b/lib/angulartics-ga.js @@ -267,7 +267,7 @@ angular.module('angulartics.google.analytics', ['angulartics']) return transactionTrackUa(transaction); - } if else (detectClassicAnalytics()) { + } else if (detectClassicAnalytics()) { return transactionTrackClassic(transaction); @@ -378,13 +378,13 @@ angular.module('angulartics.google.analytics', ['angulartics']) } - }); + } function transactionTrackClassic(transaction) { // Build our purchase array var purchase = [ - '_addTrans' + '_addTrans', transaction.id, transaction.affiliation, transaction.total, From 59bb20b495bfb646f3289d77667a9c538e59e1ac Mon Sep 17 00:00:00 2001 From: Dan Wilkerson Date: Sun, 28 Aug 2016 09:47:32 -0400 Subject: [PATCH 5/5] Tested, rebuilt, renamed classic billing fields --- dist/angulartics-ga.min.js | 2 +- dist/angulartics-ga.min.js.map | 2 +- lib/angulartics-ga.js | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dist/angulartics-ga.min.js b/dist/angulartics-ga.min.js index b24e15b..1d71e61 100644 --- a/dist/angulartics-ga.min.js +++ b/dist/angulartics-ga.min.js @@ -1,2 +1,2 @@ -!function(window,angular,undefined){"use strict";angular.module("angulartics.google.analytics",["angulartics"]).config(["$analyticsProvider",function($analyticsProvider){function dimensionsAndMetrics(properties){if(window.ga){var key,customData={};for(key in properties)key.indexOf("dimension")&&key.indexOf("metric")||(customData[key]=properties[key]);return customData}}function eventTrack(action,properties){if(!$analyticsProvider.settings.ga.disableEventTracking){if(properties&&properties.category||(properties=properties||{},properties.category="Event"),properties.value){var parsed=parseInt(properties.value,10);properties.value=isNaN(parsed)?0:parsed}if(properties.hitCallback&&"function"!=typeof properties.hitCallback&&(properties.hitCallback=null),properties.hasOwnProperty("nonInteraction")||(properties.nonInteraction=properties.noninteraction),window.ga){var eventOptions={eventCategory:properties.category,eventAction:action,eventLabel:properties.label,eventValue:properties.value,nonInteraction:properties.nonInteraction,page:properties.page||window.location.hash.substring(1)||window.location.pathname,userId:$analyticsProvider.settings.ga.userId,hitCallback:properties.hitCallback},dimsAndMets=dimensionsAndMetrics(properties);angular.extend(eventOptions,dimsAndMets),$analyticsProvider.settings.ga.transport&&angular.extend(eventOptions,$analyticsProvider.settings.ga.transport),ga("send","event",eventOptions),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){ga(accountName+".send","event",eventOptions)})}else window._gaq&&_gaq.push(["_trackEvent",properties.category,action,properties.label,properties.value,properties.nonInteraction])}}$analyticsProvider.settings.pageTracking.trackRelativePath=!0,$analyticsProvider.settings.ga={additionalAccountNames:undefined,disableEventTracking:null,disablePageTracking:null,userId:null,enhancedEcommerce:!1},$analyticsProvider.registerPageTrack(function(path,properties){if(!$analyticsProvider.settings.ga.disablePageTracking&&(window._gaq&&(_gaq.push(["_trackPageview",path]),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){_gaq.push([accountName+"._trackPageview",path])})),window.ga)){var dimsAndMets=dimensionsAndMetrics(properties);$analyticsProvider.settings.ga.userId&&ga("set","userId",$analyticsProvider.settings.ga.userId),ga("send","pageview",path,dimsAndMets),angular.forEach($analyticsProvider.settings.ga.additionalAccountNames,function(accountName){ga(accountName+".send","pageview",path,dimsAndMets)})}}),$analyticsProvider.registerEventTrack(eventTrack),$analyticsProvider.registerExceptionTrack(function(error,cause){eventTrack(error.toString(),{category:"Exceptions",label:error.stack,nonInteraction:!0})}),$analyticsProvider.registerSetUsername(function(userId){$analyticsProvider.settings.ga.userId=userId}),$analyticsProvider.registerSetUserProperties(function(properties){if(properties){var dimsAndMets=dimensionsAndMetrics(properties);ga("set",dimsAndMets)}}),$analyticsProvider.registerUserTimings(function(properties){return properties&&properties.timingCategory&&properties.timingVar&&"undefined"!=typeof properties.timingValue?void(window.ga&&ga("send","timing",properties)):void console.log("Properties timingCategory, timingVar, and timingValue are required to be set.")}),$analyticsProvider.registerTransactionTrack(function(transaction){if(!transaction.id&&transaction.id+""!="0")return console.log("Missing required field transaction.id");var product,item,i,isEnhancedEcommerce=$analyticsProvider.settings.ga.enhancedEcommerce,plugin=isEnhancedEcommerce?"ec":"ecommerce",addTransCmd=isEnhancedEcommerce?["ec:setAction","purchase"]:["ecommerce:addTransaction"],addItemCmd=isEnhancedEcommerce?"ec:addProduct":"ecommerce:addItem",dimsAndMets=dimensionsAndMetrics(transaction),purchase={id:transaction.id,affiliation:transaction.affiliation,revenue:transaction.revenue,tax:transaction.tax,shipping:transaction.shipping,coupon:transaction.coupon};for(isEnhancedEcommerce||angular.extend(purchase,dimsAndMets),addTransCmd.push(purchase),ga("require",plugin),ga.apply(this,addTransCmd),i=0;i