diff --git a/README.md b/README.md index 5205ce41..a58631ec 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,10 @@ window.ga.trackEvent('Category', 'Action', 'Label', Value)// Label and Value are window.ga.trackEvent('Category', 'Action', 'Label', Value, true)// Label, Value and newSession are optional, Value is numeric, newSession is true/false //To track custom metrics: -window.ga.trackMetric(key, Value)// Key and value are numeric type, Value is optional +//(trackMetric doesn't actually send a hit, it's behaving more like the addCustomDimension() method. +// The metric is afterwards added to every hit (view, event, error, etc...) sent, but the defined scope of the custom metric in analytics backend +// (hit or product) will determine, at processing time, which hits are associated with the metric value.) +window.ga.trackMetric(Key, Value) // Key and value are numeric type //To track an Exception: window.ga.trackException('Description', Fatal)//where Fatal is boolean @@ -91,7 +94,9 @@ window.ga.addTransaction('ID', 'Affiliation', Revenue, Tax, Shipping, 'Currency window.ga.addTransactionItem('ID', 'Name', 'SKU', 'Category', Price, Quantity, 'Currency Code')// where Price and Quantity are numeric //To add a Custom Dimension -window.ga.addCustomDimension('Key', 'Value', success, error) +//(The dimension is afterwards added to every hit (view, event, error, etc...) sent, but the defined scope of the custom dimension in analytics backend +// (hit or product) will determine, at processing time, which hits are associated with the dimension value.) +window.ga.addCustomDimension(Key, 'Value', success, error) //Key should be integer index of the dimension i.e. send `1` instead of `dimension1` for the first custom dimension you are tracking. e.g. `window.ga.addCustomDimension(1, 'Value', success, error)` //To set a UserId: @@ -125,7 +130,7 @@ window.ga.debugMode() // set's dry run mode on Android and Windows platform, so that all hits are only echoed back by the google analytics service and no actual hit is getting tracked! // **Android quirk**: verbose logging within javascript console is not supported. To see debug responses from analytics execute // `adb shell setprop log.tag.GAv4 DEBUG` and then `adb logcat -v time -s GAv4` to list messages -// (see [Android SDK Documentation on deprected Logger class](https://developers.google.com/android/reference/com/google/android/gms/analytics/Logger)) +// (see https://developers.google.com/android/reference/com/google/android/gms/analytics/Logger) //To enable/disable automatic reporting of uncaught exceptions window.ga.enableUncaughtExceptionReporting(Enable, success, error)// where Enable is boolean @@ -157,6 +162,9 @@ import { Platform } from 'ionic-angular'; } ``` +**Issue for using trackMetric in Ionic 2**: currently `@ionic-native/google-analytics` defines the typescript signature with `trackMetric(key: string, value?: any)`. +So be aware to pass the metric index as a string formatted integer and a non empty string as a value, like `window.ga.trackMetric('1', 'Value', success, error)`! + # Installing Without the CLI Copy the files manually into your project and add the following to your config.xml files: @@ -221,4 +229,7 @@ The following plugin methods are (currently) not supported by the UWP.SDKforGoog Unexpected behaviour may occur on the following methods: * `trackView()`: campaign details are currently not supported and therefore not tracked. +* `trackMetric()`: there is currently a bug in version 1.5.2 of the [UWP.SDKforGoogleAnalytics.Native package via NuGet](http://nuget.org/packages/UWP.SDKforGoogleAnalytics.Native), +that the wrong data specifier `cd` is taken for metrics, whereas `cm` should be the correct specifier. +So as long as this bug is not fixed, trackMetrics will overwrite previous addCustomDimension with same index!! diff --git a/android/UniversalAnalyticsPlugin.java b/android/UniversalAnalyticsPlugin.java index 5e12ce31..606e976e 100644 --- a/android/UniversalAnalyticsPlugin.java +++ b/android/UniversalAnalyticsPlugin.java @@ -41,6 +41,7 @@ public class UniversalAnalyticsPlugin extends CordovaPlugin { public Boolean trackerStarted = false; public Boolean debugModeEnabled = false; public HashMap customDimensions = new HashMap(); + public HashMap customMetrics = new HashMap(); public Tracker tracker; @@ -177,9 +178,9 @@ private void addCustomDimension(Integer key, String value, CallbackContext callb callbackContext.success("custom dimension started"); } - private void addCustomDimensionsToHitBuilder(T builder) { + private void addCustomDimensionsAndMetricsToHitBuilder(T builder) { //unfortunately the base HitBuilders.HitBuilder class is not public, therefore have to use reflection to use - //the common setCustomDimension (int index, String dimension) method + //the common setCustomDimension (int index, String dimension) and setCustomMetrics (int index, Float metric) methods try { Method builderMethod = builder.getClass().getMethod("setCustomDimension", Integer.TYPE, String.class); @@ -196,6 +197,23 @@ private void addCustomDimensionsToHitBuilder(T builder) { } catch (SecurityException e) { } catch (NoSuchMethodException e) { } + + try { + Method builderMethod = builder.getClass().getMethod("setCustomMetric", Integer.TYPE, Float.TYPE); + + for (Entry entry : customMetrics.entrySet()) { + Integer key = entry.getKey(); + Float value = entry.getValue(); + try { + builderMethod.invoke(builder, (key), value); + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + } + } catch (SecurityException e) { + } catch (NoSuchMethodException e) { + } } private void trackView(String screenname, String campaignUrl, boolean newSession, CallbackContext callbackContext) { @@ -208,7 +226,7 @@ private void trackView(String screenname, String campaignUrl, boolean newSession tracker.setScreenName(screenname); HitBuilders.ScreenViewBuilder hitBuilder = new HitBuilders.ScreenViewBuilder(); - addCustomDimensionsToHitBuilder(hitBuilder); + addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); if(!campaignUrl.equals("")){ hitBuilder.setCampaignParamsFromUrl(campaignUrl); @@ -234,7 +252,7 @@ private void trackEvent(String category, String action, String label, long value if (null != category && category.length() > 0) { HitBuilders.EventBuilder hitBuilder = new HitBuilders.EventBuilder(); - addCustomDimensionsToHitBuilder(hitBuilder); + addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); if(!newSession){ tracker.send(hitBuilder @@ -260,21 +278,26 @@ private void trackEvent(String category, String action, String label, long value } private void trackMetric(Integer key, String value, CallbackContext callbackContext) { - if (!trackerStarted) { - callbackContext.error("Tracker not started"); + if (key <= 0) { + callbackContext.error("Expected positive integer argument for key."); return; } - if (key >= 0) { - HitBuilders.ScreenViewBuilder hitBuilder = new HitBuilders.ScreenViewBuilder(); - tracker.send(hitBuilder - .setCustomMetric(key, Float.parseFloat(value)) - .build() - ); - callbackContext.success("Track Metric: " + key + ", value: " + value); - } else { - callbackContext.error("Expected integer key: " + key + ", and string value: " + value); + if (null == value || value.length() == 0) { + callbackContext.error("Expected non-empty string argument for value."); + return; } + + Float floatValue; + try { + floatValue = Float.parseFloat(value); + } catch (NumberFormatException e) { + callbackContext.error("Expected string formatted number for value."); + return; + } + + customMetrics.put(key, floatValue); + callbackContext.success("custom metric started"); } private void trackException(String description, Boolean fatal, CallbackContext callbackContext) { @@ -285,7 +308,7 @@ private void trackException(String description, Boolean fatal, CallbackContext c if (null != description && description.length() > 0) { HitBuilders.ExceptionBuilder hitBuilder = new HitBuilders.ExceptionBuilder(); - addCustomDimensionsToHitBuilder(hitBuilder); + addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); tracker.send(hitBuilder .setDescription(description) @@ -306,7 +329,7 @@ private void trackTiming(String category, long intervalInMilliseconds, String na if (null != category && category.length() > 0) { HitBuilders.TimingBuilder hitBuilder = new HitBuilders.TimingBuilder(); - addCustomDimensionsToHitBuilder(hitBuilder); + addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); tracker.send(hitBuilder .setCategory(category) @@ -329,7 +352,7 @@ private void addTransaction(String id, String affiliation, double revenue, doubl if (null != id && id.length() > 0) { HitBuilders.TransactionBuilder hitBuilder = new HitBuilders.TransactionBuilder(); - addCustomDimensionsToHitBuilder(hitBuilder); + addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); tracker.send(hitBuilder .setTransactionId(id) @@ -353,7 +376,7 @@ private void addTransactionItem(String id, String name, String sku, String categ if (null != id && id.length() > 0) { HitBuilders.ItemBuilder hitBuilder = new HitBuilders.ItemBuilder(); - addCustomDimensionsToHitBuilder(hitBuilder); + addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); tracker.send(hitBuilder .setTransactionId(id) diff --git a/windows/GoogleAnalyticsProxy.js b/windows/GoogleAnalyticsProxy.js index b101a6d2..4623cb9c 100644 --- a/windows/GoogleAnalyticsProxy.js +++ b/windows/GoogleAnalyticsProxy.js @@ -3,12 +3,13 @@ var _supported = null; // set to null so we can check first time var _tracker = null; var _customDimensions = {}; +var _customMetrics = {}; function isSupported() { // if not checked before, run check if (_supported === null) { _supported = (GoogleAnalytics && GoogleAnalytics.AnalyticsManager && GoogleAnalytics.AnalyticsManager.current && - GoogleAnalytics.HitBuilder); + GoogleAnalytics.HitBuilder); } return _supported; } @@ -66,12 +67,8 @@ function uncaughtExceptionHandler(err) { try { var hit = GoogleAnalytics.HitBuilder.createException(err.message, true); - // add previously added custom dimensions - for (var key in _customDimensions) { - if (_customDimensions.hasOwnProperty(key)) { - hit = hit.setCustomDimension(key, _customDimensions[key]); - } - } + // add previously added custom dimensions and metrics + hit = addCustomDimensionsAndMetrics(hit); const data = hit.build(); getTracker().send(data); @@ -81,9 +78,28 @@ function uncaughtExceptionHandler(err) { return true; } +function addCustomDimensionsAndMetrics(hitBuilder) { + if (hitBuilder) { + // add previously added custom dimensions + for (var key in _customDimensions) { + if (_customDimensions.hasOwnProperty(key)) { + hitBuilder = hitBuilder.setCustomDimension(key, _customDimensions[key]); + } + } + + // add previously added custom metrics + for (var key in _customMetrics) { + if (_customMetrics.hasOwnProperty(key)) { + hitBuilder = hitBuilder.setCustomMetric(key, _customMetrics[key]); + } + } + } + return hitBuilder; +} + module.exports = { - setOptOut: function(win, fail, args) { + setOptOut: function (win, fail, args) { if (!args || args.length === 0 || typeof args[0] !== "boolean") { fail("Expected boolean argument"); return; @@ -93,7 +109,7 @@ module.exports = { win(); }, - enableUncaughtExceptionReporting: function(win, fail, args) { + enableUncaughtExceptionReporting: function (win, fail, args) { if (!args || args.length === 0 || typeof args[0] !== "boolean") { fail("Expected boolean argument"); return; @@ -114,23 +130,23 @@ module.exports = { win(); }, - dispatch: function(win, fail, args) { + dispatch: function (win, fail, args) { getAnalyticsManager().dispatchAsync().done(win, fail); }, - debugMode: function(win, fail, args) { + debugMode: function (win, fail, args) { const ga = getAnalyticsManager(); ga.isDebug = true; // hook debug events ga.addEventListener("hitfailed", onHitFailed); ga.addEventListener("hitsent", onHitSent); - ga.addEventListener("hitmailformed", onHitMalformed); + ga.addEventListener("hitmailformed", onHitMalformed); win(); }, - startTrackerWithId: function(win, fail, args) { + startTrackerWithId: function (win, fail, args) { if (!args || args.length === 0 || args[0] === "") { fail("Expected non empty string argument"); return; @@ -140,7 +156,7 @@ module.exports = { fail("Expected numeric integer argument"); return; } - + if (isTrackerStarted()) { fail("Tracker already started!"); return; @@ -158,8 +174,8 @@ module.exports = { _tracker = ga.createTracker(args[0]); win(); }, - - setUserId: function(win, fail, args) { + + setUserId: function (win, fail, args) { if (!args || args.length === 0 || args[0] === "") { fail("Expected non empty string argument"); return; @@ -169,7 +185,7 @@ module.exports = { win(); }, - setAnonymizeIp: function(win, fail, args) { + setAnonymizeIp: function (win, fail, args) { if (!args || args.length === 0 || typeof args[0] !== "boolean") { fail("Expected boolean argument"); return; @@ -179,12 +195,12 @@ module.exports = { win(); }, - setAllowIDFACollection: function() { + setAllowIDFACollection: function () { // not supported fail("not supported on Windows platform"); }, - setAppVersion: function(win, fail, args) { + setAppVersion: function (win, fail, args) { if (!args || args.length === 0 || args[0] === "") { fail("Expected non empty string argument"); return; @@ -194,58 +210,60 @@ module.exports = { win(); }, - getVar: function(win, fail, args) { + getVar: function (win, fail, args) { if (!args || args.length === 0 || args[0] === "") { fail("Expected non empty string argument"); return; } - + const value = getTracker().get(args[0]); win(value); }, - setVar: function(win, fail, args) { + setVar: function (win, fail, args) { if (!args || args.length === 0 || args[0] === "") { fail("Expected non empty string argument"); return; } - + getTracker().set(args[0], args[1]); win(); }, - trackMetric: function(win, fail, args) { - if (!args || args.length === 0) { - fail("Expected positive numeric integer argument"); - return; - } - const key = Number.parseInt(args[0]); - if (Number.isNaN(key) || key < 0) { + trackMetric: function (win, fail, args) { + if (!args || args.length === 0 || !Number.isInteger(args[0]) || args[0] < 0) { fail("Expected positive numeric integer argument"); return; } - - var metric = Number.NaN; - if (args.length >= 2) { - metric = Number.parseFloat(String(args[1])); - } - if (Number.isNaN(metric)) { - fail("Expected numeric integer argument"); + + if (args.length < 1) { + fail("Expected second argument"); return; + } + var value = args[1]; + if (typeof args[1] !== "number") { + if (typeof args[1] !== "string") { + fail("Expected either numeric or string formatted number argument"); + return; + } + value = Number.parseFloat(args[1]); + if (isNaN(value)) { + fail("Expected either numeric or string formatted number argument"); + return; + } } - const data = GoogleAnalytics.HitBuilder.createScreenView().setCustomMetric(key, metric).build(); - getTracker().send(data); + _customMetrics[args[0]] = value; win(); }, - addCustomDimension: function(win, fail, args) { + addCustomDimension: function (win, fail, args) { if (!args || args.length === 0 || !Number.isInteger(args[0]) || args[0] < 0) { fail("Expected positive numeric integer argument"); return; } - if (args.length < 1 || args[2] === "") { + if (args.length < 1 || args[1] === "") { fail("Expected non empty string argument"); return; } @@ -254,30 +272,26 @@ module.exports = { win(); }, - addTransaction: function(win, fail, args) { + addTransaction: function (win, fail, args) { // not supported fail("not supported on Windows platform"); }, - addTransactionItem: function(win, fail, args) { + addTransactionItem: function (win, fail, args) { // not supported fail("not supported on Windows platform"); }, - trackView: function(win, fail, args) { + trackView: function (win, fail, args) { if (!args || args.length === 0 || args[0] === "") { fail("Expected non empty string argument"); return; } var hit = GoogleAnalytics.HitBuilder.createScreenView(args[0]); - - // add previously added custom dimensions - for (var key in _customDimensions) { - if (_customDimensions.hasOwnProperty(key)) { - hit = hit.setCustomDimension(key, _customDimensions[key]); - } - } + + // add previously added custom dimensions and metrics + hit = addCustomDimensionsAndMetrics(hit); if (args.length >= 2 && args[1] !== "" && getAnalyticsManager().isDebug === true) { console.warn("Campaign details not supported on Windows platform!"); @@ -292,7 +306,7 @@ module.exports = { win(); }, - trackEvent: function(win, fail, args) { + trackEvent: function (win, fail, args) { if (!args || args.length < 2 || args[0] === "" || args[1] === "") { fail("Expected non empty string argument"); return; @@ -305,19 +319,15 @@ module.exports = { var hit = GoogleAnalytics.HitBuilder.createCustomEvent(args[0], args[1], args[2] || null, args[3] || 0); - // add previously added custom dimensions - for (var key in _customDimensions) { - if (_customDimensions.hasOwnProperty(key)) { - hit = hit.setCustomDimension(key, _customDimensions[key]); - } - } + // add previously added custom dimensions and metrics + hit = addCustomDimensionsAndMetrics(hit); const data = hit.build(); getTracker().send(data); win(); }, - trackException: function(win, fail, args) { + trackException: function (win, fail, args) { if (!args || args.length === 0 || args[0] === "") { fail("Expected non empty string argument"); return; @@ -326,19 +336,15 @@ module.exports = { const fatal = ((args[1] || false) === true); var hit = GoogleAnalytics.HitBuilder.createException(args[0], fatal); - // add previously added custom dimensions - for (var key in _customDimensions) { - if (_customDimensions.hasOwnProperty(key)) { - hit = hit.setCustomDimension(key, _customDimensions[key]); - } - } + // add previously added custom dimensions and metrics + hit = addCustomDimensionsAndMetrics(hit); const data = hit.build(); getTracker().send(data); win(); }, - trackTiming: function(win, fail, args) { + trackTiming: function (win, fail, args) { if (!args || args.length === 0 || args[0] === "") { fail("Expected non empty string argument"); return; @@ -354,20 +360,16 @@ module.exports = { return; } - var hit = GoogleAnalytics.HitBuilder.createTiming(args[0], args[2] || null, - args[1] || 0, args[3] || null); + var hit = GoogleAnalytics.HitBuilder.createTiming(args[0], args[2] || null, + args[1] || 0, args[3] || null); - // add previously added custom dimensions - for (var key in _customDimensions) { - if (_customDimensions.hasOwnProperty(key)) { - hit = hit.setCustomDimension(key, _customDimensions[key]); - } - } + // add previously added custom dimensions and metrics + hit = addCustomDimensionsAndMetrics(hit); const data = hit.build(); getTracker().send(data); win(); } - + }; require("cordova/exec/proxy").add("UniversalAnalytics", module.exports); diff --git a/www/analytics.js b/www/analytics.js index 03b4a9d4..b96f9b25 100644 --- a/www/analytics.js +++ b/www/analytics.js @@ -51,7 +51,24 @@ UniversalAnalyticsPlugin.prototype.debugMode = function(success, error) { }; UniversalAnalyticsPlugin.prototype.trackMetric = function(key, value, success, error) { - cordova.exec(success, error, 'UniversalAnalytics', 'trackMetric', [key, value]); + // as key was formerly documented to be of type string, + // we need to at least accept string formatted numbers and pass the converted number + var numberKey = key; + if (typeof key === "string") { + numberKey = Number.parseInt(key); + if (isNaN(numberKey)) { + throw Error("key must be a valid integer or string formatted integer"); + } + } + + // as value was formerly documented to be of type string + // and therefore platform implementations expect value parameter of type string, + // we need to cast the value parameter to string - although gathered metrics are infact number types. + var stringValue = value; + if (typeof value !== "string") { + stringValue = String(value); + } + cordova.exec(success, error, 'UniversalAnalytics', 'trackMetric', [numberKey, stringValue]); }; UniversalAnalyticsPlugin.prototype.trackView = function(screen, campaignUrl, newSession, success, error) {