From 3c5ae80e0b5f393926db3e51a5fea21366076c2c Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 18 Jun 2015 15:43:57 -0500 Subject: [PATCH 01/21] WIP(analytics): Move client tracking to service --- config.json.example | 3 +- karma.conf.js | 1 + test/spec/services/analyticsServicesSpec.js | 191 ++++++++++++++++++ .../public/js/services/analyticsServices.js | 146 +++++++++++++ website/public/manifest.json | 1 + 5 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 test/spec/services/analyticsServicesSpec.js create mode 100644 website/public/js/services/analyticsServices.js diff --git a/config.json.example b/config.json.example index 5bc26812d0..7af203a71e 100644 --- a/config.json.example +++ b/config.json.example @@ -21,7 +21,8 @@ "NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID", "NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY", "GA_ID": "GA_ID", - "MP_ID": "MP_ID", + "MIXPANEL_TOKEN": "MIXPANEL_TOKEN", + "AMPLITUDE_KEY": "AMPLITUDE_KEY", "FLAG_REPORT_EMAIL": ["email@mod.com"], "EMAIL_SERVER": { "url": "http://example.com", diff --git a/karma.conf.js b/karma.conf.js index fcf3f9eb31..c77f1dea5c 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -45,6 +45,7 @@ module.exports = function(config) { "website/public/js/services/notificationServices.js", "common/script/public/userServices.js", "common/script/public/directives.js", + "website/public/js/services/analyticsServices.js", "website/public/js/services/groupServices.js", "website/public/js/services/memberServices.js", "website/public/js/services/guideServices.js", diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js new file mode 100644 index 0000000000..fa89b06ecb --- /dev/null +++ b/test/spec/services/analyticsServicesSpec.js @@ -0,0 +1,191 @@ +/** + * Created by Sabe on 6/11/2015. + */ +'use strict'; + +describe('Analytics Service', function () { + var analytics; + + beforeEach(function() { + inject(function(Analytics) { + analytics = Analytics; + }); + }); + + context('error handling', function() { + + before(function() { + sinon.stub(console, 'log'); + }); + + afterEach(function() { + console.log.reset(); + }); + + after(function() { + console.log.restore(); + }); + + it('does not accept tracking events without required properties', function() { + analytics.track('action'); + analytics.track({'hitType':'pageview','eventCategory':'green'}); + analytics.track({'hitType':'pageview','eventAction':'eat'}); + analytics.track({'eventCategory':'green','eventAction':'eat'}); + analytics.track({'hitType':'pageview'}); + analytics.track({'eventCategory':'green'}); + analytics.track({'eventAction':'eat'}); + expect(console.log.callCount).to.eql(7); + }); + + it('does not accept tracking events with incorrect hit type', function () { + analytics.track({'hitType':'moogly','eventCategory':'green','eventAction':'eat'}); + expect(console.log).to.have.been.calledOnce; + }); + }); + + context('Amplitude', function() { + + before(function() { + sinon.stub(amplitude, 'setUserId'); + sinon.stub(amplitude, 'logEvent'); + sinon.stub(amplitude, 'setUserProperties'); + }); + + afterEach(function() { + amplitude.setUserId.reset(); + amplitude.logEvent.reset(); + amplitude.setUserProperties.reset(); + }); + + after(function() { + amplitude.setUserId.restore(); + amplitude.logEvent.restore(); + amplitude.setUserProperties.restore(); + }); + + it('sets up tracking when user registers', function() { + analytics.register(); + expect(amplitude.setUserId).to.have.been.calledOnce; + }); + + it('sets up tracking when user logs in', function() { + analytics.login(); + expect(amplitude.setUserId).to.have.been.calledOnce; + }); + + it('tracks a simple user action', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + expect(amplitude.logEvent).to.have.been.calledOnce; + expect(amplitude.logEvent).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + }); + + it('tracks a user action with additional properties', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + expect(amplitude.logEvent).to.have.been.calledOnce; + expect(amplitude.logEvent).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + }); + + it('updates user-level properties', function() { + analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + expect(amplitude.setUserProperties).to.have.been.calledOnce; + expect(amplitude.setUserProperties).to.have.been.calledWith({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + }); + }); + + context('Google Analytics', function() { + + before(function() { + sinon.stub(ga); + }); + + afterEach(function() { + ga.reset(); + }); + + after(function() { + ga.restore(); + }); + + it('sets up tracking when user registers', function() { + analytics.register(); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set'); + }); + + it('sets up tracking when user logs in', function() { + analytics.login(); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set'); + }); + + it('tracks a simple user action', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('send',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + }); + + it('tracks a user action with additional properties', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('send',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + }); + + it('updates user-level properties', function() { + analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set',{'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + }); + }); + + context('Mixpanel', function() { + + before(function() { + sinon.stub(mixpanel, 'alias'); + sinon.stub(mixpanel, 'identify'); + sinon.stub(mixpanel, 'track'); + sinon.stub(mixpanel, 'register'); + }); + + afterEach(function() { + mixpanel.alias.reset(); + mixpanel.identify.reset(); + mixpanel.track.reset(); + mixpanel.register.reset(); + }); + + after(function() { + mixpanel.alias.restore(); + mixpanel.identify.restore(); + mixpanel.track.restore(); + mixpanel.register.restore(); + }); + + it('sets up tracking when user registers', function() { + analytics.register(); + expect(mixpanel.alias).to.have.been.calledOnce; + }); + + it('sets up tracking when user logs in', function() { + analytics.login(); + expect(mixpanel.identify).to.have.been.calledOnce; + }); + + it('tracks a simple user action', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + expect(mixpanel.track).to.have.been.calledOnce; + expect(mixpanel.track).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + }); + + it('tracks a user action with additional properties', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + expect(mixpanel.track).to.have.been.calledOnce; + expect(mixpanel.track).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + }); + + it('updates user-level properties', function() { + analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + expect(mixpanel.register).to.have.been.calledOnce; + expect(mixpanel.register).to.have.been.calledWith({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + }); + }); +}); diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js new file mode 100644 index 0000000000..050fa513b4 --- /dev/null +++ b/website/public/js/services/analyticsServices.js @@ -0,0 +1,146 @@ +/** + * Created by Sabe on 6/15/2015. + */ +'use strict'; + +angular + .module('habitrpg') + .factory('Analytics', analyticsFactory); + +analyticsFactory.$inject = [ + 'User' +]; + +function analyticsFactory(User) { + + var user = User.user; + + // Amplitude + var r = window.amplitude || {}; + r._q = []; + function a(window) {r[window] = function() {r._q.push([window].concat(Array.prototype.slice.call(arguments, 0)));}} + var i = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "setGlobalUserProperties"]; + for (var o = 0; o < i.length; o++) {a(i[o])} + window.amplitude = r; + amplitude.init(window.env.AMPLITUDE_KEY); + + // Google Analytics (aka Universal Analytics) + window['GoogleAnalyticsObject'] = 'ga'; + window['ga'] = window['ga'] || function() { + (window['ga'].q = window['ga'].q || []).push(arguments) + }, window['ga'].l = 1 * new Date(); + ga('create', window.env.GA_ID, 'auto'); + + // Mixpanel + (function(b) { + if (!b.__SV) { + var i, g; + window.mixpanel = b; + b._i = []; + b.init = function(a, e, d) { + function f(b, h) { + var a = h.split("."); + 2 == a.length && (b = b[a[0]], h = a[1]); + b[h] = function() { + b.push([h].concat(Array.prototype.slice.call(arguments, 0))) + } + } + var c = b; + "undefined" !== typeof d ? c = b[d] = [] : d = "mixpanel"; + c.people = c.people || []; + c.toString = function(b) { + var a = "mixpanel"; + "mixpanel" !== d && (a += "." + d); + b || (a += " (stub)"); + return a + }; + c.people.toString = function() { + return c.toString(1) + ".people (stub)" + }; + i = "disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" "); + for (g = 0; g < i.length; g++) f(c, i[g]); + b._i.push([a, e, d]) + }; + b.__SV = 1.2; + } + })(window.mixpanel || []); + mixpanel.init(window.env.MIXPANEL_TOKEN); + + function loadScripts() { + // Amplitude + var n = document.createElement("script"); + var s = document.getElementsByTagName("script")[0]; + n.type = "text/javascript"; + n.async = true; + n.src = "https://d24n15hnbwhuhn.cloudfront.net/libs/amplitude-2.2.0-min.gz.js"; + s.parentNode.insertBefore(n, s); + + // Google Analytics + var a = document.createElement('script'); + var m = document.getElementsByTagName('script')[0]; + a.async = 1; + a.src = '//www.google-analytics.com/analytics.js'; + m.parentNode.insertBefore(a, m); + + // Mixpanel + var g = document.createElement("script"); + var e = document.getElementsByTagName("script")[0]; + g.type = "text/javascript"; + g.async = !0; + g.src = "undefined" !== typeof MIXPANEL_CUSTOM_LIB_URL ? MIXPANEL_CUSTOM_LIB_URL : "//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js"; + e.parentNode.insertBefore(g, e); + } + + function register() { + amplitude.setUserId(user._id); + ga('set', {'userId':user._id}); + mixpanel.alias(user._id); + } + + function login() { + amplitude.setUserId(user._id); + ga('set', {'userId':user._id}); + mixpanel.identify(user._id); + } + + function track(properties) { + var REQUIRED_FIELDS = ['hitType','eventCategory','eventAction']; + var ALLOWED_HIT_TYPES = ['pageview','screenview','event','transaction','item','social','exception','timing']; + if (!_.isEqual(_.keys(_.pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) { + return console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS)); + } + if (!_.contains(ALLOWED_HIT_TYPES, properties.hitType)) { + return console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES)); + } + + amplitude.logEvent(properties.eventAction,properties); + mixpanel.track(properties.eventAction,properties); + ga('send',properties); + } + + function updateUser(properties) { + if (typeof properties === 'undefined') properties = {}; + + if (typeof user._id !== 'undefined') properties.UUID = user._id; + if (typeof user.stats.class !== 'undefined') properties.Class = user.stats.class; + if (typeof user.stats.exp !== 'undefined') properties.Experience = Math.floor(user.stats.exp); + if (typeof user.stats.gp !== 'undefined') properties.Gold = Math.floor(user.stats.gp); + if (typeof user.stats.hp !== 'undefined') properties.Health = Math.ceil(user.stats.hp); + if (typeof user.stats.lvl !== 'undefined') properties.Level = user.stats.lvl; + if (typeof user.stats.mp !== 'undefined') properties.Mana = Math.floor(user.stats.mp); + if (typeof user.contributor.level !== 'undefined') properties.contributorLevel = user.contributor.level; + if (typeof user.purchased.plan.planId !== 'undefined') properties.subscription = user.purchased.plan.planId; + + amplitude.setUserProperties(properties); + ga('set',properties); + mixpanel.register(properties); + } + + return { + loadScripts: loadScripts, + register: register, + login: login, + track: track, + updateUser: updateUser + }; +} diff --git a/website/public/manifest.json b/website/public/manifest.json index 73b7299a82..d1816718a3 100644 --- a/website/public/manifest.json +++ b/website/public/manifest.json @@ -43,6 +43,7 @@ "js/services/notificationServices.js", "common/script/public/userServices.js", "common/script/public/directives.js", + "js/services/analyticsServices.js", "js/services/groupServices.js", "js/services/memberServices.js", "js/services/guideServices.js", From 86d711bb5e56d03aa3ed5d90eb16e2f56d3c136d Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 18 Jun 2015 20:33:54 -0500 Subject: [PATCH 02/21] WIP(analytics): Remove Mixpanel --- test/spec/services/analyticsServicesSpec.js | 2 +- website/public/js/services/analyticsServices.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index fa89b06ecb..99e7fbfa5e 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -137,7 +137,7 @@ describe('Analytics Service', function () { }); }); - context('Mixpanel', function() { + context.skip('Mixpanel', function() { // Mixpanel not currently in use before(function() { sinon.stub(mixpanel, 'alias'); diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index 050fa513b4..5e0882368a 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -31,7 +31,7 @@ function analyticsFactory(User) { }, window['ga'].l = 1 * new Date(); ga('create', window.env.GA_ID, 'auto'); - // Mixpanel + /* Mixpanel - BROKEN (function(b) { if (!b.__SV) { var i, g; @@ -64,7 +64,7 @@ function analyticsFactory(User) { b.__SV = 1.2; } })(window.mixpanel || []); - mixpanel.init(window.env.MIXPANEL_TOKEN); + mixpanel.init(window.env.MIXPANEL_TOKEN); */ function loadScripts() { // Amplitude @@ -82,25 +82,25 @@ function analyticsFactory(User) { a.src = '//www.google-analytics.com/analytics.js'; m.parentNode.insertBefore(a, m); - // Mixpanel + /* Mixpanel var g = document.createElement("script"); var e = document.getElementsByTagName("script")[0]; g.type = "text/javascript"; g.async = !0; g.src = "undefined" !== typeof MIXPANEL_CUSTOM_LIB_URL ? MIXPANEL_CUSTOM_LIB_URL : "//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js"; - e.parentNode.insertBefore(g, e); + e.parentNode.insertBefore(g, e); */ } function register() { amplitude.setUserId(user._id); ga('set', {'userId':user._id}); - mixpanel.alias(user._id); + // mixpanel.alias(user._id); } function login() { amplitude.setUserId(user._id); ga('set', {'userId':user._id}); - mixpanel.identify(user._id); + // mixpanel.identify(user._id); } function track(properties) { @@ -114,7 +114,7 @@ function analyticsFactory(User) { } amplitude.logEvent(properties.eventAction,properties); - mixpanel.track(properties.eventAction,properties); + // mixpanel.track(properties.eventAction,properties); ga('send',properties); } @@ -133,7 +133,7 @@ function analyticsFactory(User) { amplitude.setUserProperties(properties); ga('set',properties); - mixpanel.register(properties); + // mixpanel.register(properties); } return { From 266122567d0cca31e84f323fbac31659824776f5 Mon Sep 17 00:00:00 2001 From: Kevin Gisi Date: Sat, 20 Jun 2015 14:25:34 -0400 Subject: [PATCH 03/21] Repaired specs (stubbing issues) --- test/spec/services/analyticsServicesSpec.js | 33 ++++----------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index 99e7fbfa5e..ee1fa936a1 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -14,15 +14,11 @@ describe('Analytics Service', function () { context('error handling', function() { - before(function() { + beforeEach(function() { sinon.stub(console, 'log'); }); afterEach(function() { - console.log.reset(); - }); - - after(function() { console.log.restore(); }); @@ -45,19 +41,13 @@ describe('Analytics Service', function () { context('Amplitude', function() { - before(function() { + beforeEach(function() { sinon.stub(amplitude, 'setUserId'); sinon.stub(amplitude, 'logEvent'); sinon.stub(amplitude, 'setUserProperties'); }); afterEach(function() { - amplitude.setUserId.reset(); - amplitude.logEvent.reset(); - amplitude.setUserProperties.reset(); - }); - - after(function() { amplitude.setUserId.restore(); amplitude.logEvent.restore(); amplitude.setUserProperties.restore(); @@ -94,16 +84,12 @@ describe('Analytics Service', function () { context('Google Analytics', function() { - before(function() { - sinon.stub(ga); + beforeEach(function() { + sinon.stub(window, 'ga'); }); afterEach(function() { - ga.reset(); - }); - - after(function() { - ga.restore(); + window.ga.restore(); }); it('sets up tracking when user registers', function() { @@ -139,7 +125,7 @@ describe('Analytics Service', function () { context.skip('Mixpanel', function() { // Mixpanel not currently in use - before(function() { + beforeEach(function() { sinon.stub(mixpanel, 'alias'); sinon.stub(mixpanel, 'identify'); sinon.stub(mixpanel, 'track'); @@ -147,13 +133,6 @@ describe('Analytics Service', function () { }); afterEach(function() { - mixpanel.alias.reset(); - mixpanel.identify.reset(); - mixpanel.track.reset(); - mixpanel.register.reset(); - }); - - after(function() { mixpanel.alias.restore(); mixpanel.identify.restore(); mixpanel.track.restore(); From ab53391feed0f9a58dda96bf8de3185e030296d0 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 20 Jun 2015 15:00:30 -0500 Subject: [PATCH 04/21] Convert analytics service to use sinon.sandbox --- test/spec/services/analyticsServicesSpec.js | 39 +++++---------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index ee1fa936a1..a03b962694 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -15,11 +15,7 @@ describe('Analytics Service', function () { context('error handling', function() { beforeEach(function() { - sinon.stub(console, 'log'); - }); - - afterEach(function() { - console.log.restore(); + sandbox.stub(console, 'log'); }); it('does not accept tracking events without required properties', function() { @@ -42,15 +38,9 @@ describe('Analytics Service', function () { context('Amplitude', function() { beforeEach(function() { - sinon.stub(amplitude, 'setUserId'); - sinon.stub(amplitude, 'logEvent'); - sinon.stub(amplitude, 'setUserProperties'); - }); - - afterEach(function() { - amplitude.setUserId.restore(); - amplitude.logEvent.restore(); - amplitude.setUserProperties.restore(); + sandbox.stub(amplitude, 'setUserId'); + sandbox.stub(amplitude, 'logEvent'); + sandbox.stub(amplitude, 'setUserProperties'); }); it('sets up tracking when user registers', function() { @@ -85,11 +75,7 @@ describe('Analytics Service', function () { context('Google Analytics', function() { beforeEach(function() { - sinon.stub(window, 'ga'); - }); - - afterEach(function() { - window.ga.restore(); + sandbox.stub(window, 'ga'); }); it('sets up tracking when user registers', function() { @@ -126,17 +112,10 @@ describe('Analytics Service', function () { context.skip('Mixpanel', function() { // Mixpanel not currently in use beforeEach(function() { - sinon.stub(mixpanel, 'alias'); - sinon.stub(mixpanel, 'identify'); - sinon.stub(mixpanel, 'track'); - sinon.stub(mixpanel, 'register'); - }); - - afterEach(function() { - mixpanel.alias.restore(); - mixpanel.identify.restore(); - mixpanel.track.restore(); - mixpanel.register.restore(); + sandbox.stub(mixpanel, 'alias'); + sandbox.stub(mixpanel, 'identify'); + sandbox.stub(mixpanel, 'track'); + sandbox.stub(mixpanel, 'register'); }); it('sets up tracking when user registers', function() { From 71fad8ca978cc0e95648b833f416fcc1262299fb Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 20 Jun 2015 15:50:06 -0500 Subject: [PATCH 05/21] Provide user for anaylitcs service test --- test/spec/services/analyticsServicesSpec.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index a03b962694..026ec77664 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -4,9 +4,17 @@ 'use strict'; describe('Analytics Service', function () { - var analytics; + var analytics, user; beforeEach(function() { + user = specHelper.newUser(); + user.contributor = { level: 1 }; + user.purchased = { plan: true }; + + module(function($provide) { + $provide.value('User', {user: user}); + }); + inject(function(Analytics) { analytics = Analytics; }); From 9008a31c4a47b1251ee8ea2abbe0d32b19250b50 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 20 Jun 2015 16:04:34 -0500 Subject: [PATCH 06/21] Add user to analytics service, rewrite updateUser to match test --- test/spec/services/analyticsServicesSpec.js | 4 ++-- .../public/js/services/analyticsServices.js | 22 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index 026ec77664..012bddea8d 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -8,8 +8,8 @@ describe('Analytics Service', function () { beforeEach(function() { user = specHelper.newUser(); - user.contributor = { level: 1 }; - user.purchased = { plan: true }; + user.contributor = {}; + user.purchased = { plan: {} }; module(function($provide) { $provide.value('User', {user: user}); diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index 5e0882368a..e1915e8f2f 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -119,17 +119,19 @@ function analyticsFactory(User) { } function updateUser(properties) { - if (typeof properties === 'undefined') properties = {}; + if (!properties) { + properties = {}; - if (typeof user._id !== 'undefined') properties.UUID = user._id; - if (typeof user.stats.class !== 'undefined') properties.Class = user.stats.class; - if (typeof user.stats.exp !== 'undefined') properties.Experience = Math.floor(user.stats.exp); - if (typeof user.stats.gp !== 'undefined') properties.Gold = Math.floor(user.stats.gp); - if (typeof user.stats.hp !== 'undefined') properties.Health = Math.ceil(user.stats.hp); - if (typeof user.stats.lvl !== 'undefined') properties.Level = user.stats.lvl; - if (typeof user.stats.mp !== 'undefined') properties.Mana = Math.floor(user.stats.mp); - if (typeof user.contributor.level !== 'undefined') properties.contributorLevel = user.contributor.level; - if (typeof user.purchased.plan.planId !== 'undefined') properties.subscription = user.purchased.plan.planId; + if (user._id) properties.UUID = user._id; + if (user.stats.class) properties.Class = user.stats.class; + if (user.stats.exp) properties.Experience = Math.floor(user.stats.exp); + if (user.stats.gp) properties.Gold = Math.floor(user.stats.gp); + if (user.stats.hp) properties.Health = Math.ceil(user.stats.hp); + if (user.stats.lvl) properties.Level = user.stats.lvl; + if (user.stats.mp) properties.Mana = Math.floor(user.stats.mp); + if (user.contributor.level) properties.contributorLevel = user.contributor.level; + if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; + } amplitude.setUserProperties(properties); ga('set',properties); From d96b3625311f6191209712c7b65f17bc2dd1364a Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sat, 20 Jun 2015 16:37:30 -0500 Subject: [PATCH 07/21] Add additional tests for updateUser --- test/spec/services/analyticsServicesSpec.js | 148 ++++++++++++++------ 1 file changed, 108 insertions(+), 40 deletions(-) diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index 012bddea8d..11054cea54 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -51,32 +51,66 @@ describe('Analytics Service', function () { sandbox.stub(amplitude, 'setUserProperties'); }); - it('sets up tracking when user registers', function() { - analytics.register(); - expect(amplitude.setUserId).to.have.been.calledOnce; + describe('register', function() { + it('sets up tracking when user registers', function() { + analytics.register(); + expect(amplitude.setUserId).to.have.been.calledOnce; + }); }); - it('sets up tracking when user logs in', function() { - analytics.login(); - expect(amplitude.setUserId).to.have.been.calledOnce; + describe('login', function() { + it('sets up tracking when user logs in', function() { + analytics.login(); + expect(amplitude.setUserId).to.have.been.calledOnce; + }); }); - it('tracks a simple user action', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); - expect(amplitude.logEvent).to.have.been.calledOnce; - expect(amplitude.logEvent).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + describe('track', function() { + it('tracks a simple user action', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + expect(amplitude.logEvent).to.have.been.calledOnce; + expect(amplitude.logEvent).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + }); + + it('tracks a user action with additional properties', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + expect(amplitude.logEvent).to.have.been.calledOnce; + expect(amplitude.logEvent).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + }); }); - it('tracks a user action with additional properties', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - expect(amplitude.logEvent).to.have.been.calledOnce; - expect(amplitude.logEvent).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - }); + describe('updateUser', function() { + it('updates user-level properties with values provided in properties', function() { + analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + expect(amplitude.setUserProperties).to.have.been.calledOnce; + expect(amplitude.setUserProperties).to.have.been.calledWith({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + }); - it('updates user-level properties', function() { - analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); - expect(amplitude.setUserProperties).to.have.been.calledOnce; - expect(amplitude.setUserProperties).to.have.been.calledWith({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + it('updates user-level properties with certain user values when no properties are provided', function() { + user._id = 'unique-user-id'; + user.stats.class = 'wizard'; + user.stats.exp = 35.7; + user.stats.gp = 43.2; + user.stats.hp = 47.8; + user.stats.lvl = 24; + user.stats.mp = 41; + user.contributor.level = 1; + user.purchased.plan.planId = 'unique-plan-id'; + + analytics.updateUser(); + expect(amplitude.setUserProperties).to.have.been.calledOnce; + expect(amplitude.setUserProperties).to.have.been.calledWith({ + UUID: 'unique-user-id', + Class: 'wizard', + Experience: 35, + Gold: 43, + Health: 48, + Level: 24, + Mana: 41, + contributorLevel: 1, + subscription: 'unique-plan-id' + }); + }); }); }); @@ -86,34 +120,68 @@ describe('Analytics Service', function () { sandbox.stub(window, 'ga'); }); - it('sets up tracking when user registers', function() { - analytics.register(); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('set'); + describe('register', function() { + it('sets up tracking when user registers', function() { + analytics.register(); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set'); + }); }); - it('sets up tracking when user logs in', function() { - analytics.login(); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('set'); + describe('login', function() { + it('sets up tracking when user logs in', function() { + analytics.login(); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set'); + }); }); - it('tracks a simple user action', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('send',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + describe('track', function() { + it('tracks a simple user action', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('send',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + }); + + it('tracks a user action with additional properties', function() { + analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('send',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + }); }); - it('tracks a user action with additional properties', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('send',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - }); + describe('updateUser', function() { + it('updates user-level properties with values provided in properties', function() { + analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set', {'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + }); - it('updates user-level properties', function() { - analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('set',{'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + it('updates user-level properties', function() { + user._id = 'unique-user-id'; + user.stats.class = 'wizard'; + user.stats.exp = 35.7; + user.stats.gp = 43.2; + user.stats.hp = 47.8; + user.stats.lvl = 24; + user.stats.mp = 41; + user.contributor.level = 1; + user.purchased.plan.planId = 'unique-plan-id'; + + analytics.updateUser(); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set',{ + UUID: 'unique-user-id', + Class: 'wizard', + Experience: 35, + Gold: 43, + Health: 48, + Level: 24, + Mana: 41, + contributorLevel: 1, + subscription: 'unique-plan-id' + }); + }); }); }); From 7ee189980786fa0741fbe9a69fea1e8e5bf49cff Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 21 Jun 2015 16:39:18 -0500 Subject: [PATCH 08/21] Refactore analytics service and tests --- test/spec/services/analyticsServicesSpec.js | 349 +++++++++--------- .../public/js/services/analyticsServices.js | 29 +- 2 files changed, 194 insertions(+), 184 deletions(-) diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index 11054cea54..798e0fab47 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -1,6 +1,3 @@ -/** - * Created by Sabe on 6/11/2015. - */ 'use strict'; describe('Analytics Service', function () { @@ -20,86 +17,185 @@ describe('Analytics Service', function () { }); }); - context('error handling', function() { - - beforeEach(function() { - sandbox.stub(console, 'log'); - }); - - it('does not accept tracking events without required properties', function() { - analytics.track('action'); - analytics.track({'hitType':'pageview','eventCategory':'green'}); - analytics.track({'hitType':'pageview','eventAction':'eat'}); - analytics.track({'eventCategory':'green','eventAction':'eat'}); - analytics.track({'hitType':'pageview'}); - analytics.track({'eventCategory':'green'}); - analytics.track({'eventAction':'eat'}); - expect(console.log.callCount).to.eql(7); - }); - - it('does not accept tracking events with incorrect hit type', function () { - analytics.track({'hitType':'moogly','eventCategory':'green','eventAction':'eat'}); - expect(console.log).to.have.been.calledOnce; - }); - }); - - context('Amplitude', function() { - - beforeEach(function() { - sandbox.stub(amplitude, 'setUserId'); - sandbox.stub(amplitude, 'logEvent'); - sandbox.stub(amplitude, 'setUserProperties'); - }); + context('functions', function() { describe('register', function() { - it('sets up tracking when user registers', function() { + + beforeEach(function() { + sandbox.stub(amplitude, 'setUserId'); + sandbox.stub(window, 'ga'); + }); + + it('sets up user with amplitude', function() { analytics.register(); expect(amplitude.setUserId).to.have.been.calledOnce; + expect(amplitude.setUserId).to.have.been.calledWith(user._id); + }); + + it('sets up user with google analytics', function() { + analytics.register(); + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set', {userId: user._id}); }); }); describe('login', function() { - it('sets up tracking when user logs in', function() { + + beforeEach(function() { + sandbox.stub(amplitude, 'setUserId'); + sandbox.stub(window, 'ga'); + }); + + it('sets up tracking for amplitude', function() { analytics.login(); + expect(amplitude.setUserId).to.have.been.calledOnce; + expect(amplitude.setUserId).to.have.been.calledWith(user._id); + }); + + it('sets up tracking for google analytics', function() { + analytics.login(); + + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set', {userId: user._id}); }); }); describe('track', function() { - it('tracks a simple user action', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); - expect(amplitude.logEvent).to.have.been.calledOnce; - expect(amplitude.logEvent).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); + + beforeEach(function() { + sandbox.stub(amplitude, 'logEvent'); + sandbox.stub(window, 'ga'); }); - it('tracks a user action with additional properties', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - expect(amplitude.logEvent).to.have.been.calledOnce; - expect(amplitude.logEvent).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); + context('succeful tracking', function() { + + it('tracks a simple user action with amplitude', function() { + var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron'}; + analytics.track(properties); + + expect(amplitude.logEvent).to.have.been.calledOnce; + expect(amplitude.logEvent).to.have.been.calledWith('cron', properties); + }); + + it('tracks a simple user action with google analytics', function() { + var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron'}; + analytics.track(properties); + + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('send', properties); + }); + + it('tracks a user action with additional properties in amplitude', function() { + var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}; + analytics.track(properties); + + expect(amplitude.logEvent).to.have.been.calledOnce; + expect(amplitude.logEvent).to.have.been.calledWith('cron', properties); + }); + + it('tracks a user action with additional properties in google analytics', function() { + var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}; + analytics.track(properties); + + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('send', properties); + }); + }); + + context('unsuccesful tracking', function() { + + beforeEach(function() { + sandbox.stub(console, 'log'); + }); + + context('events without requird properties', function() { + beforeEach(function(){ + analytics.track('action'); + analytics.track({'hitType':'pageview','eventCategory':'green'}); + analytics.track({'hitType':'pageview','eventAction':'eat'}); + analytics.track({'eventCategory':'green','eventAction':'eat'}); + analytics.track({'hitType':'pageview'}); + analytics.track({'eventCategory':'green'}); + analytics.track({'eventAction':'eat'}); + }); + + it('logs errors to console', function() { + expect(console.log.callCount).to.eql(7); + }); + + it('does not call out to amplitude', function() { + expect(amplitude.logEvent).to.not.be.called; + }); + + it('does not call out to google analytics', function() { + expect(ga).to.not.be.called; + }); + }); + + context('incorrect hit type', function() { + beforeEach(function() { + analytics.track({'hitType':'moogly','eventCategory':'green','eventAction':'eat'}); + }); + + it('logs error to console', function () { + expect(console.log).to.have.been.calledOnce; + }); + + it('does not call out to amplitude', function() { + expect(amplitude.logEvent).to.not.be.called; + }); + + it('does not call out to google analytics', function() { + expect(ga).to.not.be.called; + }); + }); }); }); describe('updateUser', function() { - it('updates user-level properties with values provided in properties', function() { - analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); - expect(amplitude.setUserProperties).to.have.been.calledOnce; - expect(amplitude.setUserProperties).to.have.been.calledWith({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); + + beforeEach(function() { + sandbox.stub(amplitude, 'setUserProperties'); + sandbox.stub(window, 'ga'); }); - it('updates user-level properties with certain user values when no properties are provided', function() { - user._id = 'unique-user-id'; - user.stats.class = 'wizard'; - user.stats.exp = 35.7; - user.stats.gp = 43.2; - user.stats.hp = 47.8; - user.stats.lvl = 24; - user.stats.mp = 41; - user.contributor.level = 1; - user.purchased.plan.planId = 'unique-plan-id'; + context('properties argument provided', function(){ + var properties = {'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}; + var expectedProperties = _.cloneDeep(properties); + expectedProperties.UUID = 'unique-user-id'; + expectedProperties.Class = 'wizard'; + expectedProperties.Experience = 35; + expectedProperties.Gold = 43; + expectedProperties.Health = 48; + expectedProperties.Level = 24; + expectedProperties.Mana = 41; - analytics.updateUser(); - expect(amplitude.setUserProperties).to.have.been.calledOnce; - expect(amplitude.setUserProperties).to.have.been.calledWith({ + beforeEach(function() { + user._id = 'unique-user-id'; + user.stats.class = 'wizard'; + user.stats.exp = 35.7; + user.stats.gp = 43.2; + user.stats.hp = 47.8; + user.stats.lvl = 24; + user.stats.mp = 41; + + analytics.updateUser(properties); + }); + + it('calls amplitude with provided properties and select user info', function() { + expect(amplitude.setUserProperties).to.have.been.calledOnce; + expect(amplitude.setUserProperties).to.have.been.calledWith(expectedProperties); + }); + + it('calls google analytics with provided properties and select user info', function() { + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set', expectedProperties); + }); + }); + + context('no properties argument provided', function() { + var expectedProperties = { UUID: 'unique-user-id', Class: 'wizard', Experience: 35, @@ -109,117 +205,32 @@ describe('Analytics Service', function () { Mana: 41, contributorLevel: 1, subscription: 'unique-plan-id' + }; + + beforeEach(function() { + user._id = 'unique-user-id'; + user.stats.class = 'wizard'; + user.stats.exp = 35.7; + user.stats.gp = 43.2; + user.stats.hp = 47.8; + user.stats.lvl = 24; + user.stats.mp = 41; + user.contributor.level = 1; + user.purchased.plan.planId = 'unique-plan-id'; + + analytics.updateUser(); + }); + + it('calls amplitude with select user info', function() { + expect(amplitude.setUserProperties).to.have.been.calledOnce; + expect(amplitude.setUserProperties).to.have.been.calledWith(expectedProperties); + }); + + it('calls google analytics with select user info', function() { + expect(ga).to.have.been.calledOnce; + expect(ga).to.have.been.calledWith('set', expectedProperties); }); }); }); }); - - context('Google Analytics', function() { - - beforeEach(function() { - sandbox.stub(window, 'ga'); - }); - - describe('register', function() { - it('sets up tracking when user registers', function() { - analytics.register(); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('set'); - }); - }); - - describe('login', function() { - it('sets up tracking when user logs in', function() { - analytics.login(); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('set'); - }); - }); - - describe('track', function() { - it('tracks a simple user action', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('send',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); - }); - - it('tracks a user action with additional properties', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('send',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - }); - }); - - describe('updateUser', function() { - it('updates user-level properties with values provided in properties', function() { - analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('set', {'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); - }); - - it('updates user-level properties', function() { - user._id = 'unique-user-id'; - user.stats.class = 'wizard'; - user.stats.exp = 35.7; - user.stats.gp = 43.2; - user.stats.hp = 47.8; - user.stats.lvl = 24; - user.stats.mp = 41; - user.contributor.level = 1; - user.purchased.plan.planId = 'unique-plan-id'; - - analytics.updateUser(); - expect(ga).to.have.been.calledOnce; - expect(ga).to.have.been.calledWith('set',{ - UUID: 'unique-user-id', - Class: 'wizard', - Experience: 35, - Gold: 43, - Health: 48, - Level: 24, - Mana: 41, - contributorLevel: 1, - subscription: 'unique-plan-id' - }); - }); - }); - }); - - context.skip('Mixpanel', function() { // Mixpanel not currently in use - - beforeEach(function() { - sandbox.stub(mixpanel, 'alias'); - sandbox.stub(mixpanel, 'identify'); - sandbox.stub(mixpanel, 'track'); - sandbox.stub(mixpanel, 'register'); - }); - - it('sets up tracking when user registers', function() { - analytics.register(); - expect(mixpanel.alias).to.have.been.calledOnce; - }); - - it('sets up tracking when user logs in', function() { - analytics.login(); - expect(mixpanel.identify).to.have.been.calledOnce; - }); - - it('tracks a simple user action', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); - expect(mixpanel.track).to.have.been.calledOnce; - expect(mixpanel.track).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron'}); - }); - - it('tracks a user action with additional properties', function() { - analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - expect(mixpanel.track).to.have.been.calledOnce; - expect(mixpanel.track).to.have.been.calledWith('cron',{'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}); - }); - - it('updates user-level properties', function() { - analytics.updateUser({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); - expect(mixpanel.register).to.have.been.calledOnce; - expect(mixpanel.register).to.have.been.calledWith({'userBoolean': false, 'userNumber': -8, 'userString': 'Enlightened'}); - }); - }); }); diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index e1915e8f2f..417b89e649 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -1,6 +1,3 @@ -/** - * Created by Sabe on 6/15/2015. - */ 'use strict'; angular @@ -119,25 +116,27 @@ function analyticsFactory(User) { } function updateUser(properties) { - if (!properties) { - properties = {}; + if (!properties) properties = {}; - if (user._id) properties.UUID = user._id; - if (user.stats.class) properties.Class = user.stats.class; - if (user.stats.exp) properties.Experience = Math.floor(user.stats.exp); - if (user.stats.gp) properties.Gold = Math.floor(user.stats.gp); - if (user.stats.hp) properties.Health = Math.ceil(user.stats.hp); - if (user.stats.lvl) properties.Level = user.stats.lvl; - if (user.stats.mp) properties.Mana = Math.floor(user.stats.mp); - if (user.contributor.level) properties.contributorLevel = user.contributor.level; - if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; - } + _gatherUserStats(user, properties); amplitude.setUserProperties(properties); ga('set',properties); // mixpanel.register(properties); } + function _gatherUserStats(user, properties) { + if (user._id) properties.UUID = user._id; + if (user.stats.class) properties.Class = user.stats.class; + if (user.stats.exp) properties.Experience = Math.floor(user.stats.exp); + if (user.stats.gp) properties.Gold = Math.floor(user.stats.gp); + if (user.stats.hp) properties.Health = Math.ceil(user.stats.hp); + if (user.stats.lvl) properties.Level = user.stats.lvl; + if (user.stats.mp) properties.Mana = Math.floor(user.stats.mp); + if (user.contributor.level) properties.contributorLevel = user.contributor.level; + if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; + } + return { loadScripts: loadScripts, register: register, From 15c08f091d004b868316955f63e77b559dd6fb0c Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 21 Jun 2015 17:07:22 -0500 Subject: [PATCH 09/21] Remove mixpanel from analytics service --- .../public/js/services/analyticsServices.js | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index 417b89e649..1587a625cb 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -28,41 +28,6 @@ function analyticsFactory(User) { }, window['ga'].l = 1 * new Date(); ga('create', window.env.GA_ID, 'auto'); - /* Mixpanel - BROKEN - (function(b) { - if (!b.__SV) { - var i, g; - window.mixpanel = b; - b._i = []; - b.init = function(a, e, d) { - function f(b, h) { - var a = h.split("."); - 2 == a.length && (b = b[a[0]], h = a[1]); - b[h] = function() { - b.push([h].concat(Array.prototype.slice.call(arguments, 0))) - } - } - var c = b; - "undefined" !== typeof d ? c = b[d] = [] : d = "mixpanel"; - c.people = c.people || []; - c.toString = function(b) { - var a = "mixpanel"; - "mixpanel" !== d && (a += "." + d); - b || (a += " (stub)"); - return a - }; - c.people.toString = function() { - return c.toString(1) + ".people (stub)" - }; - i = "disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" "); - for (g = 0; g < i.length; g++) f(c, i[g]); - b._i.push([a, e, d]) - }; - b.__SV = 1.2; - } - })(window.mixpanel || []); - mixpanel.init(window.env.MIXPANEL_TOKEN); */ - function loadScripts() { // Amplitude var n = document.createElement("script"); @@ -78,26 +43,16 @@ function analyticsFactory(User) { a.async = 1; a.src = '//www.google-analytics.com/analytics.js'; m.parentNode.insertBefore(a, m); - - /* Mixpanel - var g = document.createElement("script"); - var e = document.getElementsByTagName("script")[0]; - g.type = "text/javascript"; - g.async = !0; - g.src = "undefined" !== typeof MIXPANEL_CUSTOM_LIB_URL ? MIXPANEL_CUSTOM_LIB_URL : "//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js"; - e.parentNode.insertBefore(g, e); */ } function register() { amplitude.setUserId(user._id); ga('set', {'userId':user._id}); - // mixpanel.alias(user._id); } function login() { amplitude.setUserId(user._id); ga('set', {'userId':user._id}); - // mixpanel.identify(user._id); } function track(properties) { @@ -111,7 +66,6 @@ function analyticsFactory(User) { } amplitude.logEvent(properties.eventAction,properties); - // mixpanel.track(properties.eventAction,properties); ga('send',properties); } @@ -122,7 +76,6 @@ function analyticsFactory(User) { amplitude.setUserProperties(properties); ga('set',properties); - // mixpanel.register(properties); } function _gatherUserStats(user, properties) { From fbc466cb29d05272c760fd7458630b71375b1ceb Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 21 Jun 2015 17:07:48 -0500 Subject: [PATCH 10/21] Create private methods for analytics functions --- .../public/js/services/analyticsServices.js | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index 1587a625cb..24051a72c3 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -56,21 +56,15 @@ function analyticsFactory(User) { } function track(properties) { - var REQUIRED_FIELDS = ['hitType','eventCategory','eventAction']; - var ALLOWED_HIT_TYPES = ['pageview','screenview','event','transaction','item','social','exception','timing']; - if (!_.isEqual(_.keys(_.pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) { - return console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS)); - } - if (!_.contains(ALLOWED_HIT_TYPES, properties.hitType)) { - return console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES)); - } + if(_doesNotHaveRequiredFields(properties)) { return false; } + if(_doesNotHaveAllowedHitType(properties)) { return false; } amplitude.logEvent(properties.eventAction,properties); ga('send',properties); } function updateUser(properties) { - if (!properties) properties = {}; + properties = properties || {}; _gatherUserStats(user, properties); @@ -78,18 +72,6 @@ function analyticsFactory(User) { ga('set',properties); } - function _gatherUserStats(user, properties) { - if (user._id) properties.UUID = user._id; - if (user.stats.class) properties.Class = user.stats.class; - if (user.stats.exp) properties.Experience = Math.floor(user.stats.exp); - if (user.stats.gp) properties.Gold = Math.floor(user.stats.gp); - if (user.stats.hp) properties.Health = Math.ceil(user.stats.hp); - if (user.stats.lvl) properties.Level = user.stats.lvl; - if (user.stats.mp) properties.Mana = Math.floor(user.stats.mp); - if (user.contributor.level) properties.contributorLevel = user.contributor.level; - if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; - } - return { loadScripts: loadScripts, register: register, @@ -98,3 +80,34 @@ function analyticsFactory(User) { updateUser: updateUser }; } + +function _gatherUserStats(user, properties) { + if (user._id) properties.UUID = user._id; + if (user.stats.class) properties.Class = user.stats.class; + if (user.stats.exp) properties.Experience = Math.floor(user.stats.exp); + if (user.stats.gp) properties.Gold = Math.floor(user.stats.gp); + if (user.stats.hp) properties.Health = Math.ceil(user.stats.hp); + if (user.stats.lvl) properties.Level = user.stats.lvl; + if (user.stats.mp) properties.Mana = Math.floor(user.stats.mp); + if (user.contributor.level) properties.contributorLevel = user.contributor.level; + if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; +} + +function _doesNotHaveRequiredFields(properties) { + var REQUIRED_FIELDS = ['hitType','eventCategory','eventAction']; + + if (!_.isEqual(_.keys(_.pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) { + console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS)); + return true; + } +} + +function _doesNotHaveAllowedHitType(properties) { + var ALLOWED_HIT_TYPES = ['pageview','screenview','event','transaction','item','social','exception','timing']; + + if (!_.contains(ALLOWED_HIT_TYPES, properties.hitType)) { + console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES)); + return true; + } +} + From 34c08e941f1a387e8d07980a3dead7a1a81155f3 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 21 Jun 2015 17:16:06 -0500 Subject: [PATCH 11/21] Wrap service in anonymous function; put constants at top of file --- .../public/js/services/analyticsServices.js | 215 +++++++++--------- 1 file changed, 108 insertions(+), 107 deletions(-) diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index 24051a72c3..a97caba8d9 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -1,113 +1,114 @@ 'use strict'; -angular - .module('habitrpg') - .factory('Analytics', analyticsFactory); - -analyticsFactory.$inject = [ - 'User' -]; - -function analyticsFactory(User) { - - var user = User.user; - - // Amplitude - var r = window.amplitude || {}; - r._q = []; - function a(window) {r[window] = function() {r._q.push([window].concat(Array.prototype.slice.call(arguments, 0)));}} - var i = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "setGlobalUserProperties"]; - for (var o = 0; o < i.length; o++) {a(i[o])} - window.amplitude = r; - amplitude.init(window.env.AMPLITUDE_KEY); - - // Google Analytics (aka Universal Analytics) - window['GoogleAnalyticsObject'] = 'ga'; - window['ga'] = window['ga'] || function() { - (window['ga'].q = window['ga'].q || []).push(arguments) - }, window['ga'].l = 1 * new Date(); - ga('create', window.env.GA_ID, 'auto'); - - function loadScripts() { - // Amplitude - var n = document.createElement("script"); - var s = document.getElementsByTagName("script")[0]; - n.type = "text/javascript"; - n.async = true; - n.src = "https://d24n15hnbwhuhn.cloudfront.net/libs/amplitude-2.2.0-min.gz.js"; - s.parentNode.insertBefore(n, s); - - // Google Analytics - var a = document.createElement('script'); - var m = document.getElementsByTagName('script')[0]; - a.async = 1; - a.src = '//www.google-analytics.com/analytics.js'; - m.parentNode.insertBefore(a, m); - } - - function register() { - amplitude.setUserId(user._id); - ga('set', {'userId':user._id}); - } - - function login() { - amplitude.setUserId(user._id); - ga('set', {'userId':user._id}); - } - - function track(properties) { - if(_doesNotHaveRequiredFields(properties)) { return false; } - if(_doesNotHaveAllowedHitType(properties)) { return false; } - - amplitude.logEvent(properties.eventAction,properties); - ga('send',properties); - } - - function updateUser(properties) { - properties = properties || {}; - - _gatherUserStats(user, properties); - - amplitude.setUserProperties(properties); - ga('set',properties); - } - - return { - loadScripts: loadScripts, - register: register, - login: login, - track: track, - updateUser: updateUser - }; -} - -function _gatherUserStats(user, properties) { - if (user._id) properties.UUID = user._id; - if (user.stats.class) properties.Class = user.stats.class; - if (user.stats.exp) properties.Experience = Math.floor(user.stats.exp); - if (user.stats.gp) properties.Gold = Math.floor(user.stats.gp); - if (user.stats.hp) properties.Health = Math.ceil(user.stats.hp); - if (user.stats.lvl) properties.Level = user.stats.lvl; - if (user.stats.mp) properties.Mana = Math.floor(user.stats.mp); - if (user.contributor.level) properties.contributorLevel = user.contributor.level; - if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; -} - -function _doesNotHaveRequiredFields(properties) { +(function(){ var REQUIRED_FIELDS = ['hitType','eventCategory','eventAction']; - - if (!_.isEqual(_.keys(_.pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) { - console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS)); - return true; - } -} - -function _doesNotHaveAllowedHitType(properties) { var ALLOWED_HIT_TYPES = ['pageview','screenview','event','transaction','item','social','exception','timing']; - if (!_.contains(ALLOWED_HIT_TYPES, properties.hitType)) { - console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES)); - return true; - } -} + angular + .module('habitrpg') + .factory('Analytics', analyticsFactory); + + analyticsFactory.$inject = [ + 'User' + ]; + + function analyticsFactory(User) { + + var user = User.user; + + // Amplitude + var r = window.amplitude || {}; + r._q = []; + function a(window) {r[window] = function() {r._q.push([window].concat(Array.prototype.slice.call(arguments, 0)));}} + var i = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "setGlobalUserProperties"]; + for (var o = 0; o < i.length; o++) {a(i[o])} + window.amplitude = r; + amplitude.init(window.env.AMPLITUDE_KEY); + + // Google Analytics (aka Universal Analytics) + window['GoogleAnalyticsObject'] = 'ga'; + window['ga'] = window['ga'] || function() { + (window['ga'].q = window['ga'].q || []).push(arguments) + }, window['ga'].l = 1 * new Date(); + ga('create', window.env.GA_ID, 'auto'); + + function loadScripts() { + // Amplitude + var n = document.createElement("script"); + var s = document.getElementsByTagName("script")[0]; + n.type = "text/javascript"; + n.async = true; + n.src = "https://d24n15hnbwhuhn.cloudfront.net/libs/amplitude-2.2.0-min.gz.js"; + s.parentNode.insertBefore(n, s); + + // Google Analytics + var a = document.createElement('script'); + var m = document.getElementsByTagName('script')[0]; + a.async = 1; + a.src = '//www.google-analytics.com/analytics.js'; + m.parentNode.insertBefore(a, m); + } + + function register() { + amplitude.setUserId(user._id); + ga('set', {'userId':user._id}); + } + + function login() { + amplitude.setUserId(user._id); + ga('set', {'userId':user._id}); + } + + function track(properties) { + if(_doesNotHaveRequiredFields(properties)) { return false; } + if(_doesNotHaveAllowedHitType(properties)) { return false; } + + amplitude.logEvent(properties.eventAction,properties); + ga('send',properties); + } + + function updateUser(properties) { + properties = properties || {}; + + _gatherUserStats(user, properties); + + amplitude.setUserProperties(properties); + ga('set',properties); + } + + return { + loadScripts: loadScripts, + register: register, + login: login, + track: track, + updateUser: updateUser + }; + } + + function _gatherUserStats(user, properties) { + if (user._id) properties.UUID = user._id; + if (user.stats.class) properties.Class = user.stats.class; + if (user.stats.exp) properties.Experience = Math.floor(user.stats.exp); + if (user.stats.gp) properties.Gold = Math.floor(user.stats.gp); + if (user.stats.hp) properties.Health = Math.ceil(user.stats.hp); + if (user.stats.lvl) properties.Level = user.stats.lvl; + if (user.stats.mp) properties.Mana = Math.floor(user.stats.mp); + if (user.contributor.level) properties.contributorLevel = user.contributor.level; + if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; + } + + function _doesNotHaveRequiredFields(properties) { + if (!_.isEqual(_.keys(_.pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) { + console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS)); + return true; + } + } + + function _doesNotHaveAllowedHitType(properties) { + if (!_.contains(ALLOWED_HIT_TYPES, properties.hitType)) { + console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES)); + return true; + } + } +}()) From 9a0e31db40071b582fd14fd2ce91b03661c91872 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 22 Jun 2015 11:21:02 -0500 Subject: [PATCH 12/21] feat(analytics): Analytics service Inject Analytics service into controllers where needed. Call Analytics service instead of GA or Mixpanel for tracking within the app. --- website/public/js/app.js | 6 +++- website/public/js/controllers/authCtrl.js | 28 +++++++++---------- website/public/js/controllers/footerCtrl.js | 11 +------- website/public/js/controllers/groupsCtrl.js | 27 +++++++++--------- .../public/js/controllers/inventoryCtrl.js | 6 ++-- .../public/js/controllers/notificationCtrl.js | 6 ++-- website/public/js/controllers/rootCtrl.js | 8 +++--- website/public/js/controllers/tasksCtrl.js | 8 +++--- website/public/js/services/guideServices.js | 9 +++--- website/public/js/static.js | 6 ++-- website/views/index.jade | 5 ---- website/views/static/front.jade | 7 +---- website/views/static/layout.jade | 5 ---- 13 files changed, 56 insertions(+), 76 deletions(-) diff --git a/website/public/js/app.js b/website/public/js/app.js index fae2f42e42..7260cd0147 100644 --- a/website/public/js/app.js +++ b/website/public/js/app.js @@ -243,7 +243,7 @@ window.habitrpg = angular.module('habitrpg', .state('options.settings.notifications', { url: "/notifications", templateUrl: "partials/options.settings.notifications.html" - }) + }); var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)); if (settings && settings.auth) { @@ -252,3 +252,7 @@ window.habitrpg = angular.module('habitrpg', $httpProvider.defaults.headers.common['x-api-key'] = settings.auth.apiToken; } }]) + + .run(['Analytics', function(Analytics) { + if (window.env.NODE_ENV === 'production') Analytics.loadScripts(); + }]); diff --git a/website/public/js/controllers/authCtrl.js b/website/public/js/controllers/authCtrl.js index 48cf71cbe7..b706f27d07 100644 --- a/website/public/js/controllers/authCtrl.js +++ b/website/public/js/controllers/authCtrl.js @@ -5,8 +5,8 @@ */ angular.module('habitrpg') - .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', - function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal) { + .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', 'Analytics', + function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) { $scope.logout = function() { localStorage.clear(); @@ -47,14 +47,14 @@ angular.module('habitrpg') $http.post(url, scope.registerVals).success(function(data, status, headers, config) { runAuth(data.id, data.apiToken); if (status == 200) { - mixpanel.alias(data._id); + Analytics.register(); if (data.auth.facebook) { - mixpanel.register({'authType':'facebook','email':data.auth.facebook._json.email}) + Analytics.updateUser({'email':data.auth.facebook._json.email,'language':data.preferences.language}); + Analytics.track({'hitType':'event','eventCategory':'acquisition','eventAction':'register','authType':'facebook'}); } else { - mixpanel.register({'authType':'email','email':data.auth.local.email}) + Analytics.updateUser({'email':data.auth.local.email,'language':data.preferences.language}); + Analytics.track({'hitType':'event','eventCategory':'acquisition','eventAction':'register','authType':'email'}); } - mixpanel.register({'UUID':data._id,'language':data.preferences.language}); - mixpanel.track('Registration'); } }).error(errorAlert); }; @@ -68,15 +68,15 @@ angular.module('habitrpg') .success(function(data, status, headers, config) { runAuth(data.id, data.token); if (status == 200) { - mixpanel.identify(data.id); - mixpanel.register({'UUID':data._id}); - mixpanel.track('Login'); + Analytics.login(); + Analytics.updateUser(); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'}); } }).error(errorAlert); }; $scope.playButtonClick = function(){ - window.ga && ga('send', 'event', 'button', 'click', 'Play'); + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Play'}) if (User.authenticated()) { window.location.href = ('/' + window.location.hash); } else { @@ -144,9 +144,9 @@ angular.module('habitrpg') $http.post(ApiUrl.get() + "/api/v2/user/auth/social", auth) .success(function(data, status, headers, config) { if (status == 200) { - mixpanel.identify(data.id); - mixpanel.register({'UUID':data._id}); - mixpanel.track('Login'); + Analytics.login(); + Analytics.updateUser(); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'}); } runAuth(data.id, data.token); }).error(errorAlert); diff --git a/website/public/js/controllers/footerCtrl.js b/website/public/js/controllers/footerCtrl.js index f7fda11580..32c3055e6c 100644 --- a/website/public/js/controllers/footerCtrl.js +++ b/website/public/js/controllers/footerCtrl.js @@ -25,20 +25,11 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl) { // Stripe $.getScript('//checkout.stripe.com/v2/checkout.js'); - // Google Analytics, only in production + // Google Content Experiments if (window.env.NODE_ENV === 'production') { - // Get experiments API $.getScript('//www.google-analytics.com/cx/api.js?experiment=t-AFggRWQnuJ6Teck_x1-Q', function(){ $rootScope.variant = cxApi.chooseVariation(); $rootScope.$apply(); - - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - ga('create', window.env.GA_ID, {userId:User.user._id}); - ga('require', 'displayfeatures'); - ga('send', 'pageview'); }) } diff --git a/website/public/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js index eaf23df4d3..dc1329c981 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -286,7 +286,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }); }]) - .controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', function($scope, Groups, User, $http, ApiUrl, Notification, Members, $rootScope){ + .controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', 'Analytics', + function($scope, Groups, User, $http, ApiUrl, Notification, Members, $rootScope, Analytics){ $scope.message = {content:''}; $scope._sending = false; @@ -321,9 +322,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.message.content = ''; $scope._sending = false; if (group.privacy == 'public'){ - mixpanel.track('Group Chat',{'groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message}) + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message}); } else { - mixpanel.track('Group Chat',{'groupType':group.type,'privacy':group.privacy}) + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy}); } }, function(err){ $scope._sending = false; @@ -417,8 +418,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }]) - .controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', - function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile) { + .controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics', + function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) { $scope.groups = { guilds: Groups.myGuilds(), "public": Groups.publicGuilds() @@ -436,8 +437,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' if (confirm(window.env.t('confirmGuild'))) { group.$save(function(saved){ - if (saved.privacy == 'public') {mixpanel.track('Join Group',{'owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} - else {mixpanel.track('Join Group',{'owner':true,'groupType':'guild','privacy':saved.privacy})} + if (saved.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} + else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy})} $rootScope.hardRedirect('/#/options/groups/guilds/' + saved._id); }); } @@ -452,8 +453,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } group.$join(function(joined){ - if (joined.privacy == 'public') {mixpanel.track('Join Group',{'owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} - else {mixpanel.track('Join Group',{'owner':false,'groupType':'guild','privacy':joined.privacy})} + if (joined.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} + else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy})} $rootScope.hardRedirect('/#/options/groups/guilds/' + joined._id); }) } @@ -508,8 +509,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } ]) - .controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', 'Challenges', '$state', '$compile', - function($rootScope,$scope, Groups, User, Challenges, $state, $compile) { + .controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', 'Challenges', '$state', '$compile', 'Analytics', + function($rootScope,$scope, Groups, User, Challenges, $state, $compile, Analytics) { $scope.type = 'party'; $scope.text = window.env.t('party'); $scope.group = $rootScope.party = Groups.party(); @@ -519,7 +520,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.create = function(group){ group.$save(function(){ - mixpanel.track('Join Group',{'owner':true,'groupType':'party','privacy':'private'}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'party','privacy':'private'}); $rootScope.hardRedirect('/#/options/groups/party'); }); } @@ -527,7 +528,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.join = function(party){ var group = new Groups.Group({_id: party.id, name: party.name}); group.$join(function(){ - mixpanel.track('Join Group',{'owner':false,'groupType':'party','privacy':'private'}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'}); $rootScope.hardRedirect('/#/options/groups/party'); }); } diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js index deae46e618..afd85b625b 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -1,6 +1,6 @@ habitrpg.controller("InventoryCtrl", - ['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', - function($rootScope, $scope, Shared, $window, User, Content) { + ['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', 'Analytics', + function($rootScope, $scope, Shared, $window, User, Content, Analytics) { var user = User.user; @@ -180,7 +180,7 @@ habitrpg.controller("InventoryCtrl", $rootScope.selectedQuest = undefined; } $scope.questInit = function(){ - mixpanel.track("Quest",{"owner":true,"response":"accept","questName":$scope.selectedQuest.key}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'quest','owner':true,'response':'accept','questName':$scope.selectedQuest.key}); $rootScope.party.$questAccept({key:$scope.selectedQuest.key}, function(){ $rootScope.party.$get(); }); diff --git a/website/public/js/controllers/notificationCtrl.js b/website/public/js/controllers/notificationCtrl.js index 578654afb0..2ff163240e 100644 --- a/website/public/js/controllers/notificationCtrl.js +++ b/website/public/js/controllers/notificationCtrl.js @@ -1,8 +1,8 @@ 'use strict'; habitrpg.controller('NotificationCtrl', - ['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', - function ($scope, $rootScope, Shared, Content, User, Guide, Notification) { + ['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics', + function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics) { $rootScope.$watch('user.stats.hp', function (after, before) { if (after <= 0){ @@ -87,7 +87,7 @@ habitrpg.controller('NotificationCtrl', Notification.drop(User.user._tmp.drop.dialog); } $rootScope.playSound('Item_Drop'); - mixpanel.track("Acquire Item",{'itemName':after.key,'acquireMethod':'Drop'}) + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'acquire item','itemName':after.key,'acquireMethod':'Drop'}); }); $rootScope.$watch('user.achievements.streak', function(after, before){ diff --git a/website/public/js/controllers/rootCtrl.js b/website/public/js/controllers/rootCtrl.js index 4f6d04f630..00f9afbfa0 100644 --- a/website/public/js/controllers/rootCtrl.js +++ b/website/public/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window', - function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics) { var user = User.user; var initSticky = _.once(function(){ @@ -15,7 +15,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ - if (!!fromState.name) window.ga && ga('send', 'pageview', {page: '/#/'+toState.name}); + if (!!fromState.name) Analytics.track({'hitType':'pageview','eventCategory':'navigation','eventAction':'navigate','page':'/#/'+toState.name}); // clear inbox when entering or exiting inbox tab if (fromState.name=='options.social.inbox' || toState.name=='options.social.inbox') { User.user.ops.update && User.set({'inbox.newMessages':0}); @@ -126,7 +126,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ // Otherwise use the proper $modal.open $rootScope.openModal = function(template, options){//controller, scope, keyboard, backdrop){ if (!options) options = {}; - if (options.track) window.ga && ga('send', 'event', 'button', 'click', options.track); + if (options.track) Analytics.track(_.merge(options.track,{'hitType':'event','eventCategory':'button','eventAction':'click'})); if(template === 'newStuff') return forceLoadBailey(template, options); return $modal.open({ templateUrl: 'modals/' + template + '.html', diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js index 9f0b031e01..58a992c56d 100644 --- a/website/public/js/controllers/tasksCtrl.js +++ b/website/public/js/controllers/tasksCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', - function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks) { +habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', 'Analytics', + function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks, Analytics) { $scope.obj = User.user; // used for task-lists $scope.user = User.user; @@ -25,8 +25,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N else if (direction === 'up') $rootScope.playSound('Plus_Habit'); } User.user.ops.score({params:{id: task.id, direction:direction}}); - mixpanel.register({'Gold':Math.floor(User.user.stats.gp),'Health':Math.ceil(User.user.stats.hp),'Experience':Math.floor(User.user.stats.exp),'Level':User.user.stats.lvl,'Mana':Math.floor(User.user.stats.mp),'Class':User.user.stats.class,'subscription':User.user.purchased.plan.planId,'contributorLevel':User.user.contributor.level,'UUID':User.user._id}); - mixpanel.track('Score Task',{'taskType':task.type,'direction':direction}); + Analytics.updateUser(); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'score task','taskType':task.type,'direction':direction}); }; function addTask(addTo, listDef, task) { diff --git a/website/public/js/services/guideServices.js b/website/public/js/services/guideServices.js index 5ba95de2e0..c387414fed 100644 --- a/website/public/js/services/guideServices.js +++ b/website/public/js/services/guideServices.js @@ -5,8 +5,8 @@ */ angular.module('habitrpg').factory('Guide', -['$rootScope', 'User', '$timeout', '$state', -function($rootScope, User, $timeout, $state) { +['$rootScope', 'User', '$timeout', '$state', 'Analytics', +function($rootScope, User, $timeout, $state, Analytics) { var chapters = { intro: [ @@ -184,14 +184,13 @@ function($rootScope, User, $timeout, $state) { $state.go(step.state); return $timeout(function(){}); } - window.ga && ga('send', 'event', 'behavior', 'tour', k, i+1); - mixpanel.track('Tutorial',{'tour':k+'-web','step':i+1,'complete':false}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':false}) } step.onHide = function(){ if (step.final) { // -2 indicates complete var ups={};ups['flags.tour.'+k] = -2; User.set(ups); - mixpanel.track('Tutorial',{'tour':k+'-web','step':i+1,'complete':true}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':true}) } } }) diff --git a/website/public/js/static.js b/website/public/js/static.js index ae355a4b17..6494602e45 100644 --- a/website/public/js/static.js +++ b/website/public/js/static.js @@ -22,10 +22,10 @@ window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui. $scope.Math = window.Math; }]) -.controller("PlansCtrl", ['$rootScope', - function($rootScope) { +.controller("PlansCtrl", ['$rootScope','Analytics', + function($rootScope,Analytics) { $rootScope.clickContact = function(){ - window.ga && ga('send', 'event', 'button', 'click', 'Contact Us (Plans)'); + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Contact Us (Plans)'}) } } ]) diff --git a/website/views/index.jade b/website/views/index.jade index 52d464c1b0..504449ecc3 100644 --- a/website/views/index.jade +++ b/website/views/index.jade @@ -21,11 +21,6 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a script(type='text/javascript'). window.env = !{JSON.stringify(env)}; - script(type='text/javascript'). - (function(f,b){if(!b.__SV){var a,e,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" "); - for(g=0;g Date: Mon, 22 Jun 2015 12:52:24 -0500 Subject: [PATCH 13/21] Made analytics mock, adjusted sandbox --- karma.conf.js | 1 + test/spec/controllers/authCtrlSpec.js | 24 +++++++++++++++--------- test/spec/mocks/analyticsMock.js | 8 ++++++++ test/spec/mocks/sandbox.js | 6 +----- 4 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 test/spec/mocks/analyticsMock.js diff --git a/karma.conf.js b/karma.conf.js index c77f1dea5c..d7a0b6c316 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -36,6 +36,7 @@ module.exports = function(config) { 'common/dist/scripts/habitrpg-shared.js', "test/spec/mocks/translations.js", + "test/spec/mocks/sandbox.js", "website/public/js/env.js", diff --git a/test/spec/controllers/authCtrlSpec.js b/test/spec/controllers/authCtrlSpec.js index 70e985d76d..20706cf9c8 100644 --- a/test/spec/controllers/authCtrlSpec.js +++ b/test/spec/controllers/authCtrlSpec.js @@ -5,16 +5,22 @@ describe('Auth Controller', function() { describe('AuthCtrl', function(){ var scope, ctrl, user, $httpBackend, $window; - beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) { - $httpBackend = _$httpBackend_; - scope = $rootScope.$new(); - scope.loginUsername = 'user'; - scope.loginPassword = 'pass'; - $window = { location: { href: ""}, alert: sandbox.spy() }; - user = { user: {}, authenticate: sandbox.spy() }; + beforeEach(function(){ + module(function($provide) { + $provide.value('Analytics', analyticsMock); + }); - ctrl = $controller('AuthCtrl', {$scope: scope, $window: $window, User: user}); - })); + inject(function(_$httpBackend_, $rootScope, $controller) { + $httpBackend = _$httpBackend_; + scope = $rootScope.$new(); + scope.loginUsername = 'user'; + scope.loginPassword = 'pass'; + $window = { location: { href: ""}, alert: sandbox.spy() }; + user = { user: {}, authenticate: sandbox.spy() }; + + ctrl = $controller('AuthCtrl', {$scope: scope, $window: $window, User: user}); + }) + }); it('should log in users with correct uname / pass', function() { $httpBackend.expectPOST('/api/v2/user/auth/local').respond({id: 'abc', token: 'abc'}); diff --git a/test/spec/mocks/analyticsMock.js b/test/spec/mocks/analyticsMock.js new file mode 100644 index 0000000000..9844c6d702 --- /dev/null +++ b/test/spec/mocks/analyticsMock.js @@ -0,0 +1,8 @@ +'use strict' + +var analyticsMock = { + login: sandbox.spy(), + register: sandbox.spy(), + updateUser: sandbox.spy(), + track: sandbox.spy() +}; diff --git a/test/spec/mocks/sandbox.js b/test/spec/mocks/sandbox.js index f095eefd09..b084a232a4 100644 --- a/test/spec/mocks/sandbox.js +++ b/test/spec/mocks/sandbox.js @@ -1,8 +1,4 @@ -var sandbox; - -beforeEach(function() { - sandbox = sinon.sandbox.create(); -}); +var sandbox = sinon.sandbox.create(); afterEach(function() { sandbox.restore(); From 56a461c5137256307981d544fa99ef46b7cd825c Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 22 Jun 2015 14:14:33 -0500 Subject: [PATCH 14/21] Revert "feat(analytics): Analytics service" This reverts commit 9a0e31db40071b582fd14fd2ce91b03661c91872. --- website/public/js/app.js | 6 +--- website/public/js/controllers/authCtrl.js | 28 +++++++++---------- website/public/js/controllers/footerCtrl.js | 11 +++++++- website/public/js/controllers/groupsCtrl.js | 27 +++++++++--------- .../public/js/controllers/inventoryCtrl.js | 6 ++-- .../public/js/controllers/notificationCtrl.js | 6 ++-- website/public/js/controllers/rootCtrl.js | 8 +++--- website/public/js/controllers/tasksCtrl.js | 8 +++--- website/public/js/services/guideServices.js | 9 +++--- website/public/js/static.js | 6 ++-- website/views/index.jade | 5 ++++ website/views/static/front.jade | 7 ++++- website/views/static/layout.jade | 5 ++++ 13 files changed, 76 insertions(+), 56 deletions(-) diff --git a/website/public/js/app.js b/website/public/js/app.js index 7260cd0147..fae2f42e42 100644 --- a/website/public/js/app.js +++ b/website/public/js/app.js @@ -243,7 +243,7 @@ window.habitrpg = angular.module('habitrpg', .state('options.settings.notifications', { url: "/notifications", templateUrl: "partials/options.settings.notifications.html" - }); + }) var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)); if (settings && settings.auth) { @@ -252,7 +252,3 @@ window.habitrpg = angular.module('habitrpg', $httpProvider.defaults.headers.common['x-api-key'] = settings.auth.apiToken; } }]) - - .run(['Analytics', function(Analytics) { - if (window.env.NODE_ENV === 'production') Analytics.loadScripts(); - }]); diff --git a/website/public/js/controllers/authCtrl.js b/website/public/js/controllers/authCtrl.js index b706f27d07..48cf71cbe7 100644 --- a/website/public/js/controllers/authCtrl.js +++ b/website/public/js/controllers/authCtrl.js @@ -5,8 +5,8 @@ */ angular.module('habitrpg') - .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', 'Analytics', - function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) { + .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', + function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal) { $scope.logout = function() { localStorage.clear(); @@ -47,14 +47,14 @@ angular.module('habitrpg') $http.post(url, scope.registerVals).success(function(data, status, headers, config) { runAuth(data.id, data.apiToken); if (status == 200) { - Analytics.register(); + mixpanel.alias(data._id); if (data.auth.facebook) { - Analytics.updateUser({'email':data.auth.facebook._json.email,'language':data.preferences.language}); - Analytics.track({'hitType':'event','eventCategory':'acquisition','eventAction':'register','authType':'facebook'}); + mixpanel.register({'authType':'facebook','email':data.auth.facebook._json.email}) } else { - Analytics.updateUser({'email':data.auth.local.email,'language':data.preferences.language}); - Analytics.track({'hitType':'event','eventCategory':'acquisition','eventAction':'register','authType':'email'}); + mixpanel.register({'authType':'email','email':data.auth.local.email}) } + mixpanel.register({'UUID':data._id,'language':data.preferences.language}); + mixpanel.track('Registration'); } }).error(errorAlert); }; @@ -68,15 +68,15 @@ angular.module('habitrpg') .success(function(data, status, headers, config) { runAuth(data.id, data.token); if (status == 200) { - Analytics.login(); - Analytics.updateUser(); - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'}); + mixpanel.identify(data.id); + mixpanel.register({'UUID':data._id}); + mixpanel.track('Login'); } }).error(errorAlert); }; $scope.playButtonClick = function(){ - Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Play'}) + window.ga && ga('send', 'event', 'button', 'click', 'Play'); if (User.authenticated()) { window.location.href = ('/' + window.location.hash); } else { @@ -144,9 +144,9 @@ angular.module('habitrpg') $http.post(ApiUrl.get() + "/api/v2/user/auth/social", auth) .success(function(data, status, headers, config) { if (status == 200) { - Analytics.login(); - Analytics.updateUser(); - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'}); + mixpanel.identify(data.id); + mixpanel.register({'UUID':data._id}); + mixpanel.track('Login'); } runAuth(data.id, data.token); }).error(errorAlert); diff --git a/website/public/js/controllers/footerCtrl.js b/website/public/js/controllers/footerCtrl.js index 32c3055e6c..f7fda11580 100644 --- a/website/public/js/controllers/footerCtrl.js +++ b/website/public/js/controllers/footerCtrl.js @@ -25,11 +25,20 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl) { // Stripe $.getScript('//checkout.stripe.com/v2/checkout.js'); - // Google Content Experiments + // Google Analytics, only in production if (window.env.NODE_ENV === 'production') { + // Get experiments API $.getScript('//www.google-analytics.com/cx/api.js?experiment=t-AFggRWQnuJ6Teck_x1-Q', function(){ $rootScope.variant = cxApi.chooseVariation(); $rootScope.$apply(); + + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + ga('create', window.env.GA_ID, {userId:User.user._id}); + ga('require', 'displayfeatures'); + ga('send', 'pageview'); }) } diff --git a/website/public/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js index dc1329c981..eaf23df4d3 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -286,8 +286,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }); }]) - .controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', 'Analytics', - function($scope, Groups, User, $http, ApiUrl, Notification, Members, $rootScope, Analytics){ + .controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', function($scope, Groups, User, $http, ApiUrl, Notification, Members, $rootScope){ $scope.message = {content:''}; $scope._sending = false; @@ -322,9 +321,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.message.content = ''; $scope._sending = false; if (group.privacy == 'public'){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message}); + mixpanel.track('Group Chat',{'groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message}) } else { - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy}); + mixpanel.track('Group Chat',{'groupType':group.type,'privacy':group.privacy}) } }, function(err){ $scope._sending = false; @@ -418,8 +417,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }]) - .controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics', - function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) { + .controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', + function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile) { $scope.groups = { guilds: Groups.myGuilds(), "public": Groups.publicGuilds() @@ -437,8 +436,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' if (confirm(window.env.t('confirmGuild'))) { group.$save(function(saved){ - if (saved.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} - else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy})} + if (saved.privacy == 'public') {mixpanel.track('Join Group',{'owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} + else {mixpanel.track('Join Group',{'owner':true,'groupType':'guild','privacy':saved.privacy})} $rootScope.hardRedirect('/#/options/groups/guilds/' + saved._id); }); } @@ -453,8 +452,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } group.$join(function(joined){ - if (joined.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} - else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy})} + if (joined.privacy == 'public') {mixpanel.track('Join Group',{'owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} + else {mixpanel.track('Join Group',{'owner':false,'groupType':'guild','privacy':joined.privacy})} $rootScope.hardRedirect('/#/options/groups/guilds/' + joined._id); }) } @@ -509,8 +508,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } ]) - .controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', 'Challenges', '$state', '$compile', 'Analytics', - function($rootScope,$scope, Groups, User, Challenges, $state, $compile, Analytics) { + .controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', 'Challenges', '$state', '$compile', + function($rootScope,$scope, Groups, User, Challenges, $state, $compile) { $scope.type = 'party'; $scope.text = window.env.t('party'); $scope.group = $rootScope.party = Groups.party(); @@ -520,7 +519,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.create = function(group){ group.$save(function(){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'party','privacy':'private'}); + mixpanel.track('Join Group',{'owner':true,'groupType':'party','privacy':'private'}); $rootScope.hardRedirect('/#/options/groups/party'); }); } @@ -528,7 +527,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.join = function(party){ var group = new Groups.Group({_id: party.id, name: party.name}); group.$join(function(){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'}); + mixpanel.track('Join Group',{'owner':false,'groupType':'party','privacy':'private'}); $rootScope.hardRedirect('/#/options/groups/party'); }); } diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js index afd85b625b..deae46e618 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -1,6 +1,6 @@ habitrpg.controller("InventoryCtrl", - ['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', 'Analytics', - function($rootScope, $scope, Shared, $window, User, Content, Analytics) { + ['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', + function($rootScope, $scope, Shared, $window, User, Content) { var user = User.user; @@ -180,7 +180,7 @@ habitrpg.controller("InventoryCtrl", $rootScope.selectedQuest = undefined; } $scope.questInit = function(){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'quest','owner':true,'response':'accept','questName':$scope.selectedQuest.key}); + mixpanel.track("Quest",{"owner":true,"response":"accept","questName":$scope.selectedQuest.key}); $rootScope.party.$questAccept({key:$scope.selectedQuest.key}, function(){ $rootScope.party.$get(); }); diff --git a/website/public/js/controllers/notificationCtrl.js b/website/public/js/controllers/notificationCtrl.js index 2ff163240e..578654afb0 100644 --- a/website/public/js/controllers/notificationCtrl.js +++ b/website/public/js/controllers/notificationCtrl.js @@ -1,8 +1,8 @@ 'use strict'; habitrpg.controller('NotificationCtrl', - ['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics', - function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics) { + ['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', + function ($scope, $rootScope, Shared, Content, User, Guide, Notification) { $rootScope.$watch('user.stats.hp', function (after, before) { if (after <= 0){ @@ -87,7 +87,7 @@ habitrpg.controller('NotificationCtrl', Notification.drop(User.user._tmp.drop.dialog); } $rootScope.playSound('Item_Drop'); - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'acquire item','itemName':after.key,'acquireMethod':'Drop'}); + mixpanel.track("Acquire Item",{'itemName':after.key,'acquireMethod':'Drop'}) }); $rootScope.$watch('user.achievements.streak', function(after, before){ diff --git a/website/public/js/controllers/rootCtrl.js b/website/public/js/controllers/rootCtrl.js index 00f9afbfa0..4f6d04f630 100644 --- a/website/public/js/controllers/rootCtrl.js +++ b/website/public/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics', - function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window) { var user = User.user; var initSticky = _.once(function(){ @@ -15,7 +15,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ - if (!!fromState.name) Analytics.track({'hitType':'pageview','eventCategory':'navigation','eventAction':'navigate','page':'/#/'+toState.name}); + if (!!fromState.name) window.ga && ga('send', 'pageview', {page: '/#/'+toState.name}); // clear inbox when entering or exiting inbox tab if (fromState.name=='options.social.inbox' || toState.name=='options.social.inbox') { User.user.ops.update && User.set({'inbox.newMessages':0}); @@ -126,7 +126,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ // Otherwise use the proper $modal.open $rootScope.openModal = function(template, options){//controller, scope, keyboard, backdrop){ if (!options) options = {}; - if (options.track) Analytics.track(_.merge(options.track,{'hitType':'event','eventCategory':'button','eventAction':'click'})); + if (options.track) window.ga && ga('send', 'event', 'button', 'click', options.track); if(template === 'newStuff') return forceLoadBailey(template, options); return $modal.open({ templateUrl: 'modals/' + template + '.html', diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js index 58a992c56d..9f0b031e01 100644 --- a/website/public/js/controllers/tasksCtrl.js +++ b/website/public/js/controllers/tasksCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', 'Analytics', - function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks, Analytics) { +habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', + function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks) { $scope.obj = User.user; // used for task-lists $scope.user = User.user; @@ -25,8 +25,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N else if (direction === 'up') $rootScope.playSound('Plus_Habit'); } User.user.ops.score({params:{id: task.id, direction:direction}}); - Analytics.updateUser(); - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'score task','taskType':task.type,'direction':direction}); + mixpanel.register({'Gold':Math.floor(User.user.stats.gp),'Health':Math.ceil(User.user.stats.hp),'Experience':Math.floor(User.user.stats.exp),'Level':User.user.stats.lvl,'Mana':Math.floor(User.user.stats.mp),'Class':User.user.stats.class,'subscription':User.user.purchased.plan.planId,'contributorLevel':User.user.contributor.level,'UUID':User.user._id}); + mixpanel.track('Score Task',{'taskType':task.type,'direction':direction}); }; function addTask(addTo, listDef, task) { diff --git a/website/public/js/services/guideServices.js b/website/public/js/services/guideServices.js index c387414fed..5ba95de2e0 100644 --- a/website/public/js/services/guideServices.js +++ b/website/public/js/services/guideServices.js @@ -5,8 +5,8 @@ */ angular.module('habitrpg').factory('Guide', -['$rootScope', 'User', '$timeout', '$state', 'Analytics', -function($rootScope, User, $timeout, $state, Analytics) { +['$rootScope', 'User', '$timeout', '$state', +function($rootScope, User, $timeout, $state) { var chapters = { intro: [ @@ -184,13 +184,14 @@ function($rootScope, User, $timeout, $state, Analytics) { $state.go(step.state); return $timeout(function(){}); } - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':false}) + window.ga && ga('send', 'event', 'behavior', 'tour', k, i+1); + mixpanel.track('Tutorial',{'tour':k+'-web','step':i+1,'complete':false}); } step.onHide = function(){ if (step.final) { // -2 indicates complete var ups={};ups['flags.tour.'+k] = -2; User.set(ups); - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':true}) + mixpanel.track('Tutorial',{'tour':k+'-web','step':i+1,'complete':true}); } } }) diff --git a/website/public/js/static.js b/website/public/js/static.js index 6494602e45..ae355a4b17 100644 --- a/website/public/js/static.js +++ b/website/public/js/static.js @@ -22,10 +22,10 @@ window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui. $scope.Math = window.Math; }]) -.controller("PlansCtrl", ['$rootScope','Analytics', - function($rootScope,Analytics) { +.controller("PlansCtrl", ['$rootScope', + function($rootScope) { $rootScope.clickContact = function(){ - Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Contact Us (Plans)'}) + window.ga && ga('send', 'event', 'button', 'click', 'Contact Us (Plans)'); } } ]) diff --git a/website/views/index.jade b/website/views/index.jade index 504449ecc3..52d464c1b0 100644 --- a/website/views/index.jade +++ b/website/views/index.jade @@ -21,6 +21,11 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a script(type='text/javascript'). window.env = !{JSON.stringify(env)}; + script(type='text/javascript'). + (function(f,b){if(!b.__SV){var a,e,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" "); + for(g=0;g Date: Mon, 22 Jun 2015 15:44:33 -0500 Subject: [PATCH 15/21] WIP(analytics): Enable service --- website/public/js/app.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/public/js/app.js b/website/public/js/app.js index fae2f42e42..7260cd0147 100644 --- a/website/public/js/app.js +++ b/website/public/js/app.js @@ -243,7 +243,7 @@ window.habitrpg = angular.module('habitrpg', .state('options.settings.notifications', { url: "/notifications", templateUrl: "partials/options.settings.notifications.html" - }) + }); var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)); if (settings && settings.auth) { @@ -252,3 +252,7 @@ window.habitrpg = angular.module('habitrpg', $httpProvider.defaults.headers.common['x-api-key'] = settings.auth.apiToken; } }]) + + .run(['Analytics', function(Analytics) { + if (window.env.NODE_ENV === 'production') Analytics.loadScripts(); + }]); From 83e0eb374fcff2de48426f3eb6bff91182cf2091 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 22 Jun 2015 16:32:44 -0500 Subject: [PATCH 16/21] fix(analytics): Load scripts in factory --- website/public/js/app.js | 4 ---- website/public/js/services/analyticsServices.js | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/website/public/js/app.js b/website/public/js/app.js index 7260cd0147..822ce9601d 100644 --- a/website/public/js/app.js +++ b/website/public/js/app.js @@ -251,8 +251,4 @@ window.habitrpg = angular.module('habitrpg', $httpProvider.defaults.headers.common['x-api-user'] = settings.auth.apiId; $httpProvider.defaults.headers.common['x-api-key'] = settings.auth.apiToken; } - }]) - - .run(['Analytics', function(Analytics) { - if (window.env.NODE_ENV === 'production') Analytics.loadScripts(); }]); diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index a97caba8d9..aed208354a 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -76,6 +76,8 @@ ga('set',properties); } + if (window.env.NODE_ENV === 'production') loadScripts(); + return { loadScripts: loadScripts, register: register, @@ -110,5 +112,5 @@ return true; } } -}()) +}()); From 2ce1daffb829e3bd089beda655370f9988209000 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 22 Jun 2015 16:35:17 -0500 Subject: [PATCH 17/21] feat(analytics): call service from app --- website/public/js/controllers/authCtrl.js | 28 +++++++++---------- website/public/js/controllers/footerCtrl.js | 11 +------- website/public/js/controllers/groupsCtrl.js | 27 +++++++++--------- .../public/js/controllers/inventoryCtrl.js | 6 ++-- .../public/js/controllers/notificationCtrl.js | 6 ++-- website/public/js/controllers/rootCtrl.js | 8 +++--- website/public/js/controllers/tasksCtrl.js | 8 +++--- website/public/js/services/guideServices.js | 9 +++--- website/public/js/static.js | 6 ++-- website/views/index.jade | 5 ---- website/views/static/front.jade | 7 +---- website/views/static/layout.jade | 5 ---- 12 files changed, 51 insertions(+), 75 deletions(-) diff --git a/website/public/js/controllers/authCtrl.js b/website/public/js/controllers/authCtrl.js index 48cf71cbe7..b706f27d07 100644 --- a/website/public/js/controllers/authCtrl.js +++ b/website/public/js/controllers/authCtrl.js @@ -5,8 +5,8 @@ */ angular.module('habitrpg') - .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', - function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal) { + .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', 'Analytics', + function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) { $scope.logout = function() { localStorage.clear(); @@ -47,14 +47,14 @@ angular.module('habitrpg') $http.post(url, scope.registerVals).success(function(data, status, headers, config) { runAuth(data.id, data.apiToken); if (status == 200) { - mixpanel.alias(data._id); + Analytics.register(); if (data.auth.facebook) { - mixpanel.register({'authType':'facebook','email':data.auth.facebook._json.email}) + Analytics.updateUser({'email':data.auth.facebook._json.email,'language':data.preferences.language}); + Analytics.track({'hitType':'event','eventCategory':'acquisition','eventAction':'register','authType':'facebook'}); } else { - mixpanel.register({'authType':'email','email':data.auth.local.email}) + Analytics.updateUser({'email':data.auth.local.email,'language':data.preferences.language}); + Analytics.track({'hitType':'event','eventCategory':'acquisition','eventAction':'register','authType':'email'}); } - mixpanel.register({'UUID':data._id,'language':data.preferences.language}); - mixpanel.track('Registration'); } }).error(errorAlert); }; @@ -68,15 +68,15 @@ angular.module('habitrpg') .success(function(data, status, headers, config) { runAuth(data.id, data.token); if (status == 200) { - mixpanel.identify(data.id); - mixpanel.register({'UUID':data._id}); - mixpanel.track('Login'); + Analytics.login(); + Analytics.updateUser(); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'}); } }).error(errorAlert); }; $scope.playButtonClick = function(){ - window.ga && ga('send', 'event', 'button', 'click', 'Play'); + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Play'}) if (User.authenticated()) { window.location.href = ('/' + window.location.hash); } else { @@ -144,9 +144,9 @@ angular.module('habitrpg') $http.post(ApiUrl.get() + "/api/v2/user/auth/social", auth) .success(function(data, status, headers, config) { if (status == 200) { - mixpanel.identify(data.id); - mixpanel.register({'UUID':data._id}); - mixpanel.track('Login'); + Analytics.login(); + Analytics.updateUser(); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'}); } runAuth(data.id, data.token); }).error(errorAlert); diff --git a/website/public/js/controllers/footerCtrl.js b/website/public/js/controllers/footerCtrl.js index f7fda11580..32c3055e6c 100644 --- a/website/public/js/controllers/footerCtrl.js +++ b/website/public/js/controllers/footerCtrl.js @@ -25,20 +25,11 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl) { // Stripe $.getScript('//checkout.stripe.com/v2/checkout.js'); - // Google Analytics, only in production + // Google Content Experiments if (window.env.NODE_ENV === 'production') { - // Get experiments API $.getScript('//www.google-analytics.com/cx/api.js?experiment=t-AFggRWQnuJ6Teck_x1-Q', function(){ $rootScope.variant = cxApi.chooseVariation(); $rootScope.$apply(); - - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - ga('create', window.env.GA_ID, {userId:User.user._id}); - ga('require', 'displayfeatures'); - ga('send', 'pageview'); }) } diff --git a/website/public/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js index eaf23df4d3..dc1329c981 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -286,7 +286,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }); }]) - .controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', function($scope, Groups, User, $http, ApiUrl, Notification, Members, $rootScope){ + .controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', 'Analytics', + function($scope, Groups, User, $http, ApiUrl, Notification, Members, $rootScope, Analytics){ $scope.message = {content:''}; $scope._sending = false; @@ -321,9 +322,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.message.content = ''; $scope._sending = false; if (group.privacy == 'public'){ - mixpanel.track('Group Chat',{'groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message}) + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message}); } else { - mixpanel.track('Group Chat',{'groupType':group.type,'privacy':group.privacy}) + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy}); } }, function(err){ $scope._sending = false; @@ -417,8 +418,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }]) - .controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', - function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile) { + .controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics', + function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) { $scope.groups = { guilds: Groups.myGuilds(), "public": Groups.publicGuilds() @@ -436,8 +437,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' if (confirm(window.env.t('confirmGuild'))) { group.$save(function(saved){ - if (saved.privacy == 'public') {mixpanel.track('Join Group',{'owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} - else {mixpanel.track('Join Group',{'owner':true,'groupType':'guild','privacy':saved.privacy})} + if (saved.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} + else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy})} $rootScope.hardRedirect('/#/options/groups/guilds/' + saved._id); }); } @@ -452,8 +453,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } group.$join(function(joined){ - if (joined.privacy == 'public') {mixpanel.track('Join Group',{'owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} - else {mixpanel.track('Join Group',{'owner':false,'groupType':'guild','privacy':joined.privacy})} + if (joined.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} + else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy})} $rootScope.hardRedirect('/#/options/groups/guilds/' + joined._id); }) } @@ -508,8 +509,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } ]) - .controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', 'Challenges', '$state', '$compile', - function($rootScope,$scope, Groups, User, Challenges, $state, $compile) { + .controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', 'Challenges', '$state', '$compile', 'Analytics', + function($rootScope,$scope, Groups, User, Challenges, $state, $compile, Analytics) { $scope.type = 'party'; $scope.text = window.env.t('party'); $scope.group = $rootScope.party = Groups.party(); @@ -519,7 +520,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.create = function(group){ group.$save(function(){ - mixpanel.track('Join Group',{'owner':true,'groupType':'party','privacy':'private'}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'party','privacy':'private'}); $rootScope.hardRedirect('/#/options/groups/party'); }); } @@ -527,7 +528,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.join = function(party){ var group = new Groups.Group({_id: party.id, name: party.name}); group.$join(function(){ - mixpanel.track('Join Group',{'owner':false,'groupType':'party','privacy':'private'}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'}); $rootScope.hardRedirect('/#/options/groups/party'); }); } diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js index deae46e618..afd85b625b 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -1,6 +1,6 @@ habitrpg.controller("InventoryCtrl", - ['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', - function($rootScope, $scope, Shared, $window, User, Content) { + ['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', 'Analytics', + function($rootScope, $scope, Shared, $window, User, Content, Analytics) { var user = User.user; @@ -180,7 +180,7 @@ habitrpg.controller("InventoryCtrl", $rootScope.selectedQuest = undefined; } $scope.questInit = function(){ - mixpanel.track("Quest",{"owner":true,"response":"accept","questName":$scope.selectedQuest.key}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'quest','owner':true,'response':'accept','questName':$scope.selectedQuest.key}); $rootScope.party.$questAccept({key:$scope.selectedQuest.key}, function(){ $rootScope.party.$get(); }); diff --git a/website/public/js/controllers/notificationCtrl.js b/website/public/js/controllers/notificationCtrl.js index 578654afb0..2ff163240e 100644 --- a/website/public/js/controllers/notificationCtrl.js +++ b/website/public/js/controllers/notificationCtrl.js @@ -1,8 +1,8 @@ 'use strict'; habitrpg.controller('NotificationCtrl', - ['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', - function ($scope, $rootScope, Shared, Content, User, Guide, Notification) { + ['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics', + function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics) { $rootScope.$watch('user.stats.hp', function (after, before) { if (after <= 0){ @@ -87,7 +87,7 @@ habitrpg.controller('NotificationCtrl', Notification.drop(User.user._tmp.drop.dialog); } $rootScope.playSound('Item_Drop'); - mixpanel.track("Acquire Item",{'itemName':after.key,'acquireMethod':'Drop'}) + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'acquire item','itemName':after.key,'acquireMethod':'Drop'}); }); $rootScope.$watch('user.achievements.streak', function(after, before){ diff --git a/website/public/js/controllers/rootCtrl.js b/website/public/js/controllers/rootCtrl.js index 4f6d04f630..00f9afbfa0 100644 --- a/website/public/js/controllers/rootCtrl.js +++ b/website/public/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window', - function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics) { var user = User.user; var initSticky = _.once(function(){ @@ -15,7 +15,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ - if (!!fromState.name) window.ga && ga('send', 'pageview', {page: '/#/'+toState.name}); + if (!!fromState.name) Analytics.track({'hitType':'pageview','eventCategory':'navigation','eventAction':'navigate','page':'/#/'+toState.name}); // clear inbox when entering or exiting inbox tab if (fromState.name=='options.social.inbox' || toState.name=='options.social.inbox') { User.user.ops.update && User.set({'inbox.newMessages':0}); @@ -126,7 +126,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ // Otherwise use the proper $modal.open $rootScope.openModal = function(template, options){//controller, scope, keyboard, backdrop){ if (!options) options = {}; - if (options.track) window.ga && ga('send', 'event', 'button', 'click', options.track); + if (options.track) Analytics.track(_.merge(options.track,{'hitType':'event','eventCategory':'button','eventAction':'click'})); if(template === 'newStuff') return forceLoadBailey(template, options); return $modal.open({ templateUrl: 'modals/' + template + '.html', diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js index 9f0b031e01..58a992c56d 100644 --- a/website/public/js/controllers/tasksCtrl.js +++ b/website/public/js/controllers/tasksCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', - function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks) { +habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', 'Analytics', + function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks, Analytics) { $scope.obj = User.user; // used for task-lists $scope.user = User.user; @@ -25,8 +25,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N else if (direction === 'up') $rootScope.playSound('Plus_Habit'); } User.user.ops.score({params:{id: task.id, direction:direction}}); - mixpanel.register({'Gold':Math.floor(User.user.stats.gp),'Health':Math.ceil(User.user.stats.hp),'Experience':Math.floor(User.user.stats.exp),'Level':User.user.stats.lvl,'Mana':Math.floor(User.user.stats.mp),'Class':User.user.stats.class,'subscription':User.user.purchased.plan.planId,'contributorLevel':User.user.contributor.level,'UUID':User.user._id}); - mixpanel.track('Score Task',{'taskType':task.type,'direction':direction}); + Analytics.updateUser(); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'score task','taskType':task.type,'direction':direction}); }; function addTask(addTo, listDef, task) { diff --git a/website/public/js/services/guideServices.js b/website/public/js/services/guideServices.js index 5ba95de2e0..c387414fed 100644 --- a/website/public/js/services/guideServices.js +++ b/website/public/js/services/guideServices.js @@ -5,8 +5,8 @@ */ angular.module('habitrpg').factory('Guide', -['$rootScope', 'User', '$timeout', '$state', -function($rootScope, User, $timeout, $state) { +['$rootScope', 'User', '$timeout', '$state', 'Analytics', +function($rootScope, User, $timeout, $state, Analytics) { var chapters = { intro: [ @@ -184,14 +184,13 @@ function($rootScope, User, $timeout, $state) { $state.go(step.state); return $timeout(function(){}); } - window.ga && ga('send', 'event', 'behavior', 'tour', k, i+1); - mixpanel.track('Tutorial',{'tour':k+'-web','step':i+1,'complete':false}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':false}) } step.onHide = function(){ if (step.final) { // -2 indicates complete var ups={};ups['flags.tour.'+k] = -2; User.set(ups); - mixpanel.track('Tutorial',{'tour':k+'-web','step':i+1,'complete':true}); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':true}) } } }) diff --git a/website/public/js/static.js b/website/public/js/static.js index ae355a4b17..6494602e45 100644 --- a/website/public/js/static.js +++ b/website/public/js/static.js @@ -22,10 +22,10 @@ window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui. $scope.Math = window.Math; }]) -.controller("PlansCtrl", ['$rootScope', - function($rootScope) { +.controller("PlansCtrl", ['$rootScope','Analytics', + function($rootScope,Analytics) { $rootScope.clickContact = function(){ - window.ga && ga('send', 'event', 'button', 'click', 'Contact Us (Plans)'); + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Contact Us (Plans)'}) } } ]) diff --git a/website/views/index.jade b/website/views/index.jade index 52d464c1b0..504449ecc3 100644 --- a/website/views/index.jade +++ b/website/views/index.jade @@ -21,11 +21,6 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a script(type='text/javascript'). window.env = !{JSON.stringify(env)}; - script(type='text/javascript'). - (function(f,b){if(!b.__SV){var a,e,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" "); - for(g=0;g Date: Mon, 22 Jun 2015 18:30:47 -0500 Subject: [PATCH 18/21] fix(analytics): Define Analytics on scope --- website/public/js/controllers/rootCtrl.js | 1 + website/public/manifest.json | 2 ++ 2 files changed, 3 insertions(+) diff --git a/website/public/js/controllers/rootCtrl.js b/website/public/js/controllers/rootCtrl.js index 00f9afbfa0..ef5c5aa5e8 100644 --- a/website/public/js/controllers/rootCtrl.js +++ b/website/public/js/controllers/rootCtrl.js @@ -29,6 +29,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.settings = User.settings; $rootScope.Shared = Shared; $rootScope.Content = Content; + $rootScope.Analytics = Analytics; $rootScope.env = window.env; $rootScope.Math = Math; $rootScope.Groups = Groups; diff --git a/website/public/manifest.json b/website/public/manifest.json index d1816718a3..b379990191 100644 --- a/website/public/manifest.json +++ b/website/public/manifest.json @@ -106,6 +106,7 @@ "bower_components/angular-loading-bar/build/loading-bar.js", "js/env.js", "js/static.js", + "js/services/analyticsServices.js", "js/services/notificationServices.js", "common/script/public/userServices.js", "js/controllers/authCtrl.js", @@ -132,6 +133,7 @@ "bower_components/angular-loading-bar/build/loading-bar.js", "js/env.js", "js/static.js", + "js/services/analyticsServices.js", "js/services/notificationServices.js", "common/script/public/userServices.js", "js/controllers/authCtrl.js", From f8e3120b3a9bb014d827e65ea4fe066d0337e5ff Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 22 Jun 2015 18:49:15 -0500 Subject: [PATCH 19/21] fix(analytics): Move landing page tracking ...to AuthCtrl --- website/public/js/controllers/authCtrl.js | 2 ++ website/views/static/front.jade | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/website/public/js/controllers/authCtrl.js b/website/public/js/controllers/authCtrl.js index b706f27d07..0a893fdf3b 100644 --- a/website/public/js/controllers/authCtrl.js +++ b/website/public/js/controllers/authCtrl.js @@ -8,6 +8,8 @@ angular.module('habitrpg') .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', 'Analytics', function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) { + Analytics.track({'hitType':'pageview','eventCategory':'page','eventAction':'landing page','page':'/static/front'}); + $scope.logout = function() { localStorage.clear(); window.location.href = '/logout'; diff --git a/website/views/static/front.jade b/website/views/static/front.jade index f518838463..ffa0ee3e87 100644 --- a/website/views/static/front.jade +++ b/website/views/static/front.jade @@ -33,9 +33,6 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.1/ui-bootstrap.min.js') script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.1/ui-bootstrap-tpls.min.js') - script(type='text/javascript'). - Analytics.track({'hitType':'pageview','eventCategory':'page','eventAction':'landing page','page':'/static/front'}); - body(ng-controller='AuthCtrl') include ./login-modal include ../shared/header/avatar From 8b04768b374b8661366e52f5497f0f432de21b37 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 23 Jun 2015 09:30:18 -0500 Subject: [PATCH 20/21] fix(analytics): Resolve errors Should correct 400 Bad Request errors with Amplitude and cannot read undefined errors in tmp_static_front. --- .../public/js/services/analyticsServices.js | 20 ++++++++++--------- website/src/middleware.js | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index aed208354a..d39da7df15 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -88,15 +88,17 @@ } function _gatherUserStats(user, properties) { - if (user._id) properties.UUID = user._id; - if (user.stats.class) properties.Class = user.stats.class; - if (user.stats.exp) properties.Experience = Math.floor(user.stats.exp); - if (user.stats.gp) properties.Gold = Math.floor(user.stats.gp); - if (user.stats.hp) properties.Health = Math.ceil(user.stats.hp); - if (user.stats.lvl) properties.Level = user.stats.lvl; - if (user.stats.mp) properties.Mana = Math.floor(user.stats.mp); - if (user.contributor.level) properties.contributorLevel = user.contributor.level; - if (user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; + if (user._id) properties.user_id = user._id; + if (user.stats) { + properties.Class = user.stats.class; + properties.Experience = Math.floor(user.stats.exp); + properties.Gold = Math.floor(user.stats.gp); + properties.Health = Math.ceil(user.stats.hp); + properties.Level = user.stats.lvl; + properties.Mana = Math.floor(user.stats.mp); + } + if (user.contributor && user.contributor.level) properties.contributorLevel = user.contributor.level; + if (user.purchased && user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId; } function _doesNotHaveRequiredFields(properties) { diff --git a/website/src/middleware.js b/website/src/middleware.js index abd98ea048..05e755e77f 100644 --- a/website/src/middleware.js +++ b/website/src/middleware.js @@ -180,7 +180,7 @@ module.exports.locals = function(req, res, next) { language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined); var tavern = require('./models/group').tavern; - var envVars = _.pick(nconf.get(), 'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY'.split(' ')); + var envVars = _.pick(nconf.get(), 'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ')); res.locals.habitrpg = _.merge(envVars, { IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), getManifestFiles: getManifestFiles, From fcd278c2f611f59a8df7beb567b6aa5d549a6b00 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 23 Jun 2015 10:11:03 -0500 Subject: [PATCH 21/21] fix(analytics): Pass tests Restores the "UUID" property so that tests no longer fail. Corrects various typos in analytics service test. Moves landing page tracking to the Jade file to avoid duplicate tracking. --- test/spec/services/analyticsServicesSpec.js | 32 +++++++++---------- website/public/js/controllers/authCtrl.js | 3 +- .../public/js/services/analyticsServices.js | 2 +- website/views/static/front.jade | 1 + 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/test/spec/services/analyticsServicesSpec.js b/test/spec/services/analyticsServicesSpec.js index 798e0fab47..935ace5900 100644 --- a/test/spec/services/analyticsServicesSpec.js +++ b/test/spec/services/analyticsServicesSpec.js @@ -26,13 +26,13 @@ describe('Analytics Service', function () { sandbox.stub(window, 'ga'); }); - it('sets up user with amplitude', function() { + it('sets up user with Amplitude', function() { analytics.register(); expect(amplitude.setUserId).to.have.been.calledOnce; expect(amplitude.setUserId).to.have.been.calledWith(user._id); }); - it('sets up user with google analytics', function() { + it('sets up user with Google Analytics', function() { analytics.register(); expect(ga).to.have.been.calledOnce; expect(ga).to.have.been.calledWith('set', {userId: user._id}); @@ -68,9 +68,9 @@ describe('Analytics Service', function () { sandbox.stub(window, 'ga'); }); - context('succeful tracking', function() { + context('successful tracking', function() { - it('tracks a simple user action with amplitude', function() { + it('tracks a simple user action with Amplitude', function() { var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron'}; analytics.track(properties); @@ -78,7 +78,7 @@ describe('Analytics Service', function () { expect(amplitude.logEvent).to.have.been.calledWith('cron', properties); }); - it('tracks a simple user action with google analytics', function() { + it('tracks a simple user action with Google Analytics', function() { var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron'}; analytics.track(properties); @@ -86,7 +86,7 @@ describe('Analytics Service', function () { expect(ga).to.have.been.calledWith('send', properties); }); - it('tracks a user action with additional properties in amplitude', function() { + it('tracks a user action with additional properties in Amplitude', function() { var properties = {'hitType':'event','eventCategory':'behavior','eventAction':'cron','booleanProperty':true,'numericProperty':17,'stringProperty':'bagel'}; analytics.track(properties); @@ -103,13 +103,13 @@ describe('Analytics Service', function () { }); }); - context('unsuccesful tracking', function() { + context('unsuccessful tracking', function() { beforeEach(function() { sandbox.stub(console, 'log'); }); - context('events without requird properties', function() { + context('events without required properties', function() { beforeEach(function(){ analytics.track('action'); analytics.track({'hitType':'pageview','eventCategory':'green'}); @@ -124,11 +124,11 @@ describe('Analytics Service', function () { expect(console.log.callCount).to.eql(7); }); - it('does not call out to amplitude', function() { + it('does not call out to Amplitude', function() { expect(amplitude.logEvent).to.not.be.called; }); - it('does not call out to google analytics', function() { + it('does not call out to Google Analytics', function() { expect(ga).to.not.be.called; }); }); @@ -142,11 +142,11 @@ describe('Analytics Service', function () { expect(console.log).to.have.been.calledOnce; }); - it('does not call out to amplitude', function() { + it('does not call out to Amplitude', function() { expect(amplitude.logEvent).to.not.be.called; }); - it('does not call out to google analytics', function() { + it('does not call out to Google Analytics', function() { expect(ga).to.not.be.called; }); }); @@ -183,12 +183,12 @@ describe('Analytics Service', function () { analytics.updateUser(properties); }); - it('calls amplitude with provided properties and select user info', function() { + it('calls Amplitude with provided properties and select user info', function() { expect(amplitude.setUserProperties).to.have.been.calledOnce; expect(amplitude.setUserProperties).to.have.been.calledWith(expectedProperties); }); - it('calls google analytics with provided properties and select user info', function() { + it('calls Google Analytics with provided properties and select user info', function() { expect(ga).to.have.been.calledOnce; expect(ga).to.have.been.calledWith('set', expectedProperties); }); @@ -221,12 +221,12 @@ describe('Analytics Service', function () { analytics.updateUser(); }); - it('calls amplitude with select user info', function() { + it('calls Amplitude with select user info', function() { expect(amplitude.setUserProperties).to.have.been.calledOnce; expect(amplitude.setUserProperties).to.have.been.calledWith(expectedProperties); }); - it('calls google analytics with select user info', function() { + it('calls Google Analytics with select user info', function() { expect(ga).to.have.been.calledOnce; expect(ga).to.have.been.calledWith('set', expectedProperties); }); diff --git a/website/public/js/controllers/authCtrl.js b/website/public/js/controllers/authCtrl.js index 0a893fdf3b..eb62d87d52 100644 --- a/website/public/js/controllers/authCtrl.js +++ b/website/public/js/controllers/authCtrl.js @@ -7,8 +7,7 @@ angular.module('habitrpg') .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', 'Analytics', function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) { - - Analytics.track({'hitType':'pageview','eventCategory':'page','eventAction':'landing page','page':'/static/front'}); + $scope.Analytics = Analytics; $scope.logout = function() { localStorage.clear(); diff --git a/website/public/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js index d39da7df15..462397954c 100644 --- a/website/public/js/services/analyticsServices.js +++ b/website/public/js/services/analyticsServices.js @@ -88,7 +88,7 @@ } function _gatherUserStats(user, properties) { - if (user._id) properties.user_id = user._id; + if (user._id) properties.UUID = user._id; if (user.stats) { properties.Class = user.stats.class; properties.Experience = Math.floor(user.stats.exp); diff --git a/website/views/static/front.jade b/website/views/static/front.jade index ffa0ee3e87..830d110b4b 100644 --- a/website/views/static/front.jade +++ b/website/views/static/front.jade @@ -39,6 +39,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') include ../shared/mixins include ../shared/modals/members .mobile-container + div(ng-init='Analytics.track({"hitType":"pageview","eventCategory":"page","eventAction":"landing page","page":"/static/front"});') header#header nav.navbar.navbar-default.navbar-static-top .container-fluid