mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-05-22 05:38:46 +00:00
Merge pull request #5591 from crookedneighbor/analytics-service-server-side
Analytics service - server side
This commit is contained in:
commit
4c316cab72
12 changed files with 903 additions and 98 deletions
|
|
@ -442,7 +442,7 @@ api.wrap = (user, main=true) ->
|
|||
user.preferences.sleep = !user.preferences.sleep
|
||||
cb? null, {}
|
||||
|
||||
revive: (req, cb) ->
|
||||
revive: (req, cb, analytics) ->
|
||||
return cb?({code:400, message: "Cannot revive if not dead"}) unless user.stats.hp <= 0
|
||||
|
||||
# Reset stats after death
|
||||
|
|
@ -471,7 +471,15 @@ api.wrap = (user, main=true) ->
|
|||
user.items.gear.equipped[item.type] = "#{item.type}_base_0" if user.items.gear.equipped[item.type] is lostItem
|
||||
user.items.gear.costume[item.type] = "#{item.type}_base_0" if user.items.gear.costume[item.type] is lostItem
|
||||
user.markModified? 'items.gear'
|
||||
mixpanel?.track('Death',{'lostItem':lostItem})
|
||||
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
lostItem: lostItem,
|
||||
gaLabel: lostItem,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('Death', analyticsData)
|
||||
|
||||
cb? (if item then {code:200,message: i18n.t('messageLostItem', {itemText: item.text(req.language)}, req.language)} else null), user
|
||||
|
||||
reset: (req, cb) ->
|
||||
|
|
@ -497,7 +505,7 @@ api.wrap = (user, main=true) ->
|
|||
user.preferences.costume = false
|
||||
cb? null, user
|
||||
|
||||
reroll: (req, cb, ga) ->
|
||||
reroll: (req, cb, analytics) ->
|
||||
if user.balance < 1
|
||||
return cb? {code:401,message: i18n.t('notEnoughGems', req.language)}
|
||||
user.balance--
|
||||
|
|
@ -505,19 +513,37 @@ api.wrap = (user, main=true) ->
|
|||
unless task.type is 'reward'
|
||||
task.value = 0
|
||||
user.stats.hp = 50
|
||||
cb? null, user
|
||||
mixpanel?.track("Acquire Item",{'itemName':'Fortify','acquireMethod':'Gems','gemCost':4})
|
||||
ga?.event('behavior', 'gems', 'reroll').send()
|
||||
|
||||
rebirth: (req, cb, ga) ->
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
acquireMethod: 'Gems',
|
||||
gemCost: 4,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('Fortify Potion', analyticsData)
|
||||
|
||||
cb? null, user
|
||||
|
||||
rebirth: (req, cb, analytics) ->
|
||||
# Cost is 8 Gems ($2)
|
||||
if (user.balance < 2 && user.stats.lvl < api.maxLevel)
|
||||
return cb? {code:401,message: i18n.t('notEnoughGems', req.language)}
|
||||
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
category: 'behavior'
|
||||
}
|
||||
# only charge people if they are under the max level - ryan
|
||||
if user.stats.lvl < api.maxLevel
|
||||
user.balance -= 2
|
||||
mixpanel?.track("Acquire Item",{'itemName':'Rebirth','acquireMethod':'Gems','gemCost':8})
|
||||
ga?.event('behavior', 'gems', 'rebirth').send()
|
||||
analyticsData.acquireMethod = 'Gems'
|
||||
analyticsData.gemCost = 8
|
||||
else
|
||||
analyticsData.gemCost = 0
|
||||
analyticsData.acquireMethod = '> 100'
|
||||
|
||||
analytics?.track('Rebirth', analyticsData)
|
||||
|
||||
# Save off user's level, for calculating achievement eligibility later
|
||||
lvl = api.capByLevel(user.stats.lvl)
|
||||
# Turn tasks yellow, zero out streaks
|
||||
|
|
@ -766,7 +792,7 @@ api.wrap = (user, main=true) ->
|
|||
cb? {code:200,message}, _.pick(user,$w 'items stats')
|
||||
|
||||
# buy is for using Gold, purchase is for Gems (I know, I know...)
|
||||
purchase: (req, cb, ga) ->
|
||||
purchase: (req, cb, analytics) ->
|
||||
{type,key} = req.params
|
||||
|
||||
if type is 'gems' and key is 'gem'
|
||||
|
|
@ -778,7 +804,16 @@ api.wrap = (user, main=true) ->
|
|||
user.balance += .25
|
||||
user.purchased.plan.gemsBought++
|
||||
user.stats.gp -= convRate
|
||||
mixpanel?.track("Acquire Item",{'itemName':key,'acquireMethod':'Gold','goldCost':convRate})
|
||||
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
itemKey: key,
|
||||
acquireMethod: 'Gold',
|
||||
goldCost: convRate,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('purchase gems', analyticsData)
|
||||
|
||||
return cb? {code:200,message:"+1 Gem"}, _.pick(user,$w 'stats balance')
|
||||
|
||||
return cb?({code:404,message:":type must be in [eggs,hatchingPotions,food,quests,gear]"},req) unless type in ['eggs','hatchingPotions','food','quests','gear']
|
||||
|
|
@ -796,11 +831,20 @@ api.wrap = (user, main=true) ->
|
|||
else
|
||||
user.items[type][key] = 0 unless user.items[type][key] > 0
|
||||
user.items[type][key]++
|
||||
mixpanel?.track("Acquire Item",{'itemName':key,'acquireMethod':'Gems','gemCost':item.value})
|
||||
cb? null, _.pick(user,$w 'items balance')
|
||||
ga?.event('behavior', 'gems', key).send()
|
||||
|
||||
releasePets: (req, cb) ->
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
itemKey: key,
|
||||
itemType: 'Market',
|
||||
acquireMethod: 'Gems',
|
||||
gemCost: item.value,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('acquire item', analyticsData)
|
||||
|
||||
cb? null, _.pick(user,$w 'items balance')
|
||||
|
||||
releasePets: (req, cb, analytics) ->
|
||||
if user.balance < 1
|
||||
return cb? {code:401,message: i18n.t('notEnoughGems', req.language)}
|
||||
else
|
||||
|
|
@ -811,10 +855,17 @@ api.wrap = (user, main=true) ->
|
|||
user.achievements.beastMasterCount = 0
|
||||
user.achievements.beastMasterCount++
|
||||
user.items.currentPet = ""
|
||||
cb? null, user
|
||||
mixpanel?.track("Acquire Item",{'itemName':'Kennel Key','acquireMethod':'Gems','gemCost':4})
|
||||
|
||||
releaseMounts: (req, cb) ->
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
acquireMethod: 'Gems',
|
||||
gemCost: 4,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('release pets', analyticsData)
|
||||
cb? null, user
|
||||
|
||||
releaseMounts: (req, cb, analytics) ->
|
||||
if user.balance < 1
|
||||
return cb? {code:401,message: i18n.t('notEnoughGems', req.language)}
|
||||
else
|
||||
|
|
@ -825,8 +876,16 @@ api.wrap = (user, main=true) ->
|
|||
if not user.achievements.mountMasterCount
|
||||
user.achievements.mountMasterCount = 0
|
||||
user.achievements.mountMasterCount++
|
||||
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
acquireMethod: 'Gems',
|
||||
gemCost: 4,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('release mounts', analyticsData)
|
||||
|
||||
cb? null, user
|
||||
mixpanel?.track("Acquire Item",{'itemName':'Kennel Key','acquireMethod':'Gems','gemCost':4})
|
||||
|
||||
releaseBoth: (req, cb) ->
|
||||
if user.balance < 1.5 and not user.achievements.triadBingo
|
||||
|
|
@ -834,7 +893,13 @@ api.wrap = (user, main=true) ->
|
|||
else
|
||||
giveTriadBingo = true
|
||||
if not user.achievements.triadBingo
|
||||
mixpanel?.track("Acquire Item",{'itemName':'Kennel Key','acquireMethod':'Gems','gemCost':6})
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
acquireMethod: 'Gems',
|
||||
gemCost: 6,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('release pets & mounts', analyticsData)
|
||||
user.balance -= 1.5
|
||||
user.items.currentMount = ""
|
||||
user.items.currentPet = ""
|
||||
|
|
@ -855,7 +920,7 @@ api.wrap = (user, main=true) ->
|
|||
cb? null, user
|
||||
|
||||
# buy is for gear, purchase is for gem-purchaseables (i know, I know...)
|
||||
buy: (req, cb) ->
|
||||
buy: (req, cb, analytics) ->
|
||||
{key} = req.params
|
||||
|
||||
item = if key is 'potion' then content.potion
|
||||
|
|
@ -896,10 +961,19 @@ api.wrap = (user, main=true) ->
|
|||
message ?= i18n.t('messageBought', {itemText: item.text(req.language)}, req.language)
|
||||
if item.last then user.fns.ultimateGear()
|
||||
user.stats.gp -= item.value
|
||||
mixpanel?.track("Acquire Item",{'itemName':key,'acquireMethod':'Gold','goldCost':item.value})
|
||||
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
itemKey: key,
|
||||
acquireMethod: 'Gold',
|
||||
goldCost: item.value,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('acquire item', analyticsData)
|
||||
|
||||
cb? {code:200, message}, _.pick(user,$w 'items achievements stats flags')
|
||||
|
||||
buyQuest: (req, cb) ->
|
||||
buyQuest: (req, cb, analytics) ->
|
||||
{key} = req.params
|
||||
item = content.quests[key]
|
||||
return cb?({code:404, message:"Quest '#{key} not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content.coffee)"}) unless item
|
||||
|
|
@ -909,9 +983,18 @@ api.wrap = (user, main=true) ->
|
|||
user.items.quests[item.key] ?= 0
|
||||
user.items.quests[item.key] += 1
|
||||
user.stats.gp -= item.goldValue
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
itemKey: item.key,
|
||||
itemType: 'Market',
|
||||
goldCost: item.goldValue,
|
||||
acquireMethod: 'Gold',
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('acquire item', analyticsData)
|
||||
cb? {code:200, message}, user.items.quests
|
||||
|
||||
buyMysterySet: (req, cb)->
|
||||
buyMysterySet: (req, cb, analytics)->
|
||||
return cb?({code:401, message:"You don't have enough Mystic Hourglasses"}) unless user.purchased.plan.consecutive.trinkets>0
|
||||
mysterySet = content.timeTravelerStore(user.items.gear.owned)?[req.params.key]
|
||||
if window?.confirm?
|
||||
|
|
@ -919,7 +1002,15 @@ api.wrap = (user, main=true) ->
|
|||
return cb?({code:404, message:"Mystery set not found, or set already owned"}) unless mysterySet
|
||||
_.each mysterySet.items, (i)->
|
||||
user.items.gear.owned[i.key]=true
|
||||
mixpanel?.track("Acquire Item",{'itemName':i.key,'acquireMethod':'Hourglass'})
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
itemKey: i.key,
|
||||
itemType: 'Subscriber Gear',
|
||||
acquireMethod: 'Hourglass',
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('acquire item', analyticsData)
|
||||
|
||||
user.purchased.plan.consecutive.trinkets--
|
||||
cb? null, _.pick(user,$w 'items purchased.plan.consecutive')
|
||||
|
||||
|
|
@ -963,7 +1054,7 @@ api.wrap = (user, main=true) ->
|
|||
user.items.hatchingPotions[hatchingPotion]--
|
||||
cb? {code:200, message:i18n.t('messageHatched', req.language)}, user.items
|
||||
|
||||
unlock: (req, cb, ga) ->
|
||||
unlock: (req, cb, analytics) ->
|
||||
{path} = req.query
|
||||
fullSet = ~path.indexOf(",")
|
||||
cost =
|
||||
|
|
@ -990,17 +1081,36 @@ api.wrap = (user, main=true) ->
|
|||
user.fns.dotSet "purchased." + path, true
|
||||
user.balance -= cost
|
||||
if ~path.indexOf('gear.') then user.markModified? 'gear.owned' else user.markModified? 'purchased'
|
||||
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
itemKey: path,
|
||||
itemType: 'customization',
|
||||
acquireMethod: 'Gems',
|
||||
gemCost: (cost/.25),
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('acquire item', analyticsData)
|
||||
|
||||
cb? null, _.pick(user,$w 'purchased preferences items')
|
||||
mixpanel?.track("Acquire Item",{'itemName':'Customizations','acquireMethod':'Gems','gemCost':(cost / .25)})
|
||||
ga?.event('behavior', 'gems', path).send()
|
||||
|
||||
# ------
|
||||
# Classes
|
||||
# ------
|
||||
|
||||
changeClass: (req, cb, ga) ->
|
||||
changeClass: (req, cb, analytics) ->
|
||||
klass = req.query?.class
|
||||
if klass in ['warrior','rogue','wizard','healer']
|
||||
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
class: klass,
|
||||
acquireMethod: 'Gems',
|
||||
gemCost: 3,
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('change class', analyticsData)
|
||||
|
||||
user.stats.class = klass
|
||||
user.flags.classSelected = true
|
||||
# Clear their gear and equip their new class's gear (can still equip old gear from inventory)
|
||||
|
|
@ -1031,8 +1141,6 @@ api.wrap = (user, main=true) ->
|
|||
user.balance -= .75
|
||||
_.merge user.stats, {str: 0, con: 0, per: 0, int: 0, points: api.capByLevel(user.stats.lvl)}
|
||||
user.flags.classSelected = false
|
||||
mixpanel?.track("Acquire Item",{'itemName':klass,'acquireMethod':'Gems','gemCost':3})
|
||||
ga?.event('behavior', 'gems', 'changeClass').send()
|
||||
#'stats.points': this is handled on the server
|
||||
cb? null, _.pick(user,$w 'stats flags items preferences')
|
||||
|
||||
|
|
@ -1058,13 +1166,21 @@ api.wrap = (user, main=true) ->
|
|||
user.markModified? 'items.special.valentineReceived'
|
||||
cb? null, 'items.special'
|
||||
|
||||
openMysteryItem: (req,cb,ga) ->
|
||||
openMysteryItem: (req,cb,analytics) ->
|
||||
item = user.purchased.plan?.mysteryItems?.shift()
|
||||
return cb?(code:400,message:"Empty") unless item
|
||||
item = content.gear.flat[item]
|
||||
user.items.gear.owned[item.key] = true
|
||||
user.markModified? 'purchased.plan.mysteryItems'
|
||||
item.type = 'Mystery'
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
itemKey: item,
|
||||
itemType: 'Subscriber Gear',
|
||||
acquireMethod: 'Subscriber',
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('open mystery item', analyticsData)
|
||||
(user._tmp?={}).drop = item if typeof window != 'undefined'
|
||||
cb? null, user.items.gear.owned
|
||||
|
||||
|
|
@ -1460,7 +1576,7 @@ api.wrap = (user, main=true) ->
|
|||
else "str" # if all else fails, dump into STR
|
||||
)()]++
|
||||
|
||||
updateStats: (stats, req) ->
|
||||
updateStats: (stats, req, analytics) ->
|
||||
# Game Over (death)
|
||||
return user.stats.hp=0 if stats.hp <= 0
|
||||
|
||||
|
|
@ -1514,7 +1630,13 @@ api.wrap = (user, main=true) ->
|
|||
user.items.quests[k]++
|
||||
(user.flags.levelDrops ?= {})[k] = true
|
||||
user.markModified? 'flags.levelDrops'
|
||||
mixpanel?.track("Acquire Item",{'itemName':k,'acquireMethod':'Drop'})
|
||||
analyticsData = {
|
||||
uuid: user._id,
|
||||
itemKey: k,
|
||||
acquireMethod: 'Level Drop',
|
||||
category: 'behavior'
|
||||
}
|
||||
analytics?.track('acquire item', analyticsData)
|
||||
user._tmp.drop = {type: 'Quest', key: k}
|
||||
if !user.flags.rebirthEnabled and (user.stats.lvl >= 50 or user.achievements.beastMaster)
|
||||
user.flags.rebirthEnabled = true
|
||||
|
|
@ -1695,8 +1817,17 @@ api.wrap = (user, main=true) ->
|
|||
# Analytics
|
||||
user.flags.cronCount?=0
|
||||
user.flags.cronCount++
|
||||
options.mixpanel?.track('Cron',{'distinct_id':user._id,'resting':user.preferences.sleep})
|
||||
options.ga?.event('behavior', 'cron', 'cron', user.flags.cronCount).send(); #TODO userId for cohort
|
||||
|
||||
analyticsData = {
|
||||
category: 'behavior',
|
||||
gaLabel: 'Cron Count',
|
||||
gaValue: user.flags.cronCount,
|
||||
uuid: user._id,
|
||||
user: user,
|
||||
resting: user.preferences.sleep,
|
||||
cronCount: user.flags.cronCount
|
||||
}
|
||||
options.analytics?.track('Cron', analyticsData)
|
||||
|
||||
# After all is said and done, progress up user's effect on quest, return those values & reset the user's
|
||||
progress = user.party.quest.progress; _progress = _.cloneDeep progress
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"main": "./website/src/server.js",
|
||||
"dependencies": {
|
||||
"amazon-payments": "0.0.4",
|
||||
"amplitude": "^1.0.6",
|
||||
"async": "~0.9.0",
|
||||
"aws-sdk": "^2.0.25",
|
||||
"babel": "^5.5.4",
|
||||
|
|
@ -40,7 +41,6 @@
|
|||
"lodash": "~2.4.1",
|
||||
"loggly": "~1.0.8",
|
||||
"method-override": "~2.2.0",
|
||||
"mixpanel": "^0.2.1",
|
||||
"moment": "~2.8.3",
|
||||
"mongoose": "~3.8.23",
|
||||
"mongoose-id-autoinc": "~2013.7.14-4",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,31 @@ gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
|
|||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:server_side', ['test:prepare:build'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin('mocha test/server_side'),
|
||||
(err, stdout, stderr) => {
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:server_side:safe', ['test:prepare:build'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin('mocha test/server_side'),
|
||||
(err, stdout, stderr) => {
|
||||
testResults.push({
|
||||
suite: 'Servser Side Specs\t',
|
||||
pass: testCount(stdout, /(\d+) passing/),
|
||||
fail: testCount(stderr, /(\d+) failing/),
|
||||
pend: testCount(stdout, /(\d+) pending/)
|
||||
});
|
||||
cb();
|
||||
}
|
||||
);
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api', ['test:prepare:mongo'], (cb) => {
|
||||
let runner = exec(
|
||||
|
|
@ -193,6 +218,7 @@ gulp.task('test:e2e:safe', ['test:prepare'], (cb) => {
|
|||
|
||||
gulp.task('test', [
|
||||
'test:common:safe',
|
||||
'test:server_side:safe',
|
||||
'test:karma:safe',
|
||||
'test:api:safe',
|
||||
'test:e2e:safe'
|
||||
|
|
|
|||
435
test/server_side/analytics.test.js
Normal file
435
test/server_side/analytics.test.js
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
var sinon = require('sinon');
|
||||
var chai = require("chai")
|
||||
chai.use(require("sinon-chai"))
|
||||
var expect = chai.expect
|
||||
var rewire = require('rewire');
|
||||
|
||||
describe('analytics', function() {
|
||||
// Mocks
|
||||
var amplitudeMock = sinon.stub();
|
||||
var googleAnalyticsMock = sinon.stub();
|
||||
var amplitudeTrack = sinon.stub();
|
||||
var googleEvent = sinon.stub().returns({
|
||||
send: function() { }
|
||||
});
|
||||
var googleItem = sinon.stub().returns({
|
||||
send: function() { }
|
||||
});
|
||||
var googleTransaction = sinon.stub().returns({
|
||||
item: googleItem
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
amplitudeMock.reset();
|
||||
amplitudeTrack.reset();
|
||||
googleEvent.reset();
|
||||
googleTransaction.reset();
|
||||
googleItem.reset();
|
||||
});
|
||||
|
||||
describe('init', function() {
|
||||
var analytics = rewire('../../website/src/analytics');
|
||||
|
||||
it('throws an error if no options are passed in', function() {
|
||||
expect(analytics).to.throw('No options provided');
|
||||
});
|
||||
|
||||
it('registers amplitude with token', function() {
|
||||
analytics.__set__('Amplitude', amplitudeMock);
|
||||
var options = {
|
||||
amplitudeToken: 'token'
|
||||
};
|
||||
analytics(options);
|
||||
|
||||
expect(amplitudeMock).to.be.calledOnce;
|
||||
expect(amplitudeMock).to.be.calledWith('token');
|
||||
});
|
||||
|
||||
it('registers google analytics with token', function() {
|
||||
analytics.__set__('googleAnalytics', googleAnalyticsMock);
|
||||
var options = {
|
||||
googleAnalytics: 'token'
|
||||
};
|
||||
analytics(options);
|
||||
|
||||
expect(googleAnalyticsMock).to.be.calledOnce;
|
||||
expect(googleAnalyticsMock).to.be.calledWith('token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('track', function() {
|
||||
|
||||
var analyticsData, event_type;
|
||||
var analytics = rewire('../../website/src/analytics');
|
||||
var initializedAnalytics;
|
||||
|
||||
beforeEach(function() {
|
||||
analytics.__set__('Amplitude', amplitudeMock);
|
||||
initializedAnalytics = analytics({amplitudeToken: 'token'});
|
||||
analytics.__set__('amplitude.track', amplitudeTrack);
|
||||
analytics.__set__('ga.event', googleEvent);
|
||||
|
||||
event_type = 'Cron';
|
||||
analyticsData = {
|
||||
category: 'behavior',
|
||||
uuid: 'unique-user-id',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
}
|
||||
});
|
||||
|
||||
context('Amplitude', function() {
|
||||
it('tracks event in amplitude', function() {
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'Cron',
|
||||
user_id: 'unique-user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for gear if itemKey is provided', function() {
|
||||
analyticsData.itemKey = 'headAccessory_special_foxEars'
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'Cron',
|
||||
user_id: 'unique-user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
itemKey: 'headAccessory_special_foxEars',
|
||||
itemName: 'Fox Ears',
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for egg if itemKey is provided', function() {
|
||||
analyticsData.itemKey = 'Wolf'
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'Cron',
|
||||
user_id: 'unique-user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
itemKey: 'Wolf',
|
||||
itemName: 'Wolf Egg',
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for food if itemKey is provided', function() {
|
||||
analyticsData.itemKey = 'Cake_Skeleton'
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'Cron',
|
||||
user_id: 'unique-user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
itemKey: 'Cake_Skeleton',
|
||||
itemName: 'Bare Bones Cake',
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for hatching potion if itemKey is provided', function() {
|
||||
analyticsData.itemKey = 'Golden'
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'Cron',
|
||||
user_id: 'unique-user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
itemKey: 'Golden',
|
||||
itemName: 'Golden Hatching Potion',
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for quest if itemKey is provided', function() {
|
||||
analyticsData.itemKey = 'atom1'
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'Cron',
|
||||
user_id: 'unique-user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
itemKey: 'atom1',
|
||||
itemName: 'Attack of the Mundane, Part 1: Dish Disaster!',
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for purchased spell if itemKey is provided', function() {
|
||||
analyticsData.itemKey = 'seafoam'
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'Cron',
|
||||
user_id: 'unique-user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
itemKey: 'seafoam',
|
||||
itemName: 'Seafoam',
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sends user data if provided', function() {
|
||||
var stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 };
|
||||
var user = {
|
||||
stats: stats,
|
||||
contributor: { level: 1 },
|
||||
purchased: { plan: { planId: 'foo-plan' } }
|
||||
};
|
||||
|
||||
analyticsData.user = user;
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'Cron',
|
||||
user_id: 'unique-user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5
|
||||
},
|
||||
user_properties: {
|
||||
Class: 'wizard',
|
||||
Experience: 5,
|
||||
Gold: 23,
|
||||
Health: 10,
|
||||
Level: 4,
|
||||
Mana: 30,
|
||||
contributorLevel: 1,
|
||||
subscription: 'foo-plan'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Google Analytics', function() {
|
||||
it('tracks event in google analytics', function() {
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(googleEvent).to.be.calledOnce;
|
||||
expect(googleEvent).to.be.calledWith({
|
||||
ec: 'behavior',
|
||||
ea: 'Cron'
|
||||
});
|
||||
});
|
||||
|
||||
it('if itemKey property is provided, use as label', function() {
|
||||
analyticsData.itemKey = 'some item';
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(googleEvent).to.be.calledOnce;
|
||||
expect(googleEvent).to.be.calledWith({
|
||||
ec: 'behavior',
|
||||
ea: 'Cron',
|
||||
el: 'some item'
|
||||
});
|
||||
});
|
||||
|
||||
it('if gaLabel property is provided, use as label (overrides itemKey)', function() {
|
||||
analyticsData.value = 'some value';
|
||||
analyticsData.itemKey = 'some item';
|
||||
analyticsData.gaLabel = 'some label';
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(googleEvent).to.be.calledOnce;
|
||||
expect(googleEvent).to.be.calledWith({
|
||||
ec: 'behavior',
|
||||
ea: 'Cron',
|
||||
el: 'some label'
|
||||
});
|
||||
});
|
||||
|
||||
it('if goldCost property is provided, use as value', function() {
|
||||
analyticsData.goldCost = 5;
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(googleEvent).to.be.calledOnce;
|
||||
expect(googleEvent).to.be.calledWith({
|
||||
ec: 'behavior',
|
||||
ea: 'Cron',
|
||||
ev: 5
|
||||
});
|
||||
});
|
||||
|
||||
it('if gemCost property is provided, use as value (overrides goldCost)', function() {
|
||||
analyticsData.gemCost = 7;
|
||||
analyticsData.goldCost = 5;
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(googleEvent).to.be.calledOnce;
|
||||
expect(googleEvent).to.be.calledWith({
|
||||
ec: 'behavior',
|
||||
ea: 'Cron',
|
||||
ev: 7
|
||||
});
|
||||
});
|
||||
|
||||
it('if gaValue property is provided, use as value (overrides gemCost)', function() {
|
||||
analyticsData.gemCost = 7;
|
||||
analyticsData.gaValue = 5;
|
||||
|
||||
initializedAnalytics.track(event_type, analyticsData);
|
||||
|
||||
expect(googleEvent).to.be.calledOnce;
|
||||
expect(googleEvent).to.be.calledWith({
|
||||
ec: 'behavior',
|
||||
ea: 'Cron',
|
||||
ev: 5
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('trackPurchase', function() {
|
||||
|
||||
var purchaseData;
|
||||
|
||||
var analytics = rewire('../../website/src/analytics');
|
||||
var initializedAnalytics;
|
||||
|
||||
beforeEach(function() {
|
||||
analytics.__set__('Amplitude', amplitudeMock);
|
||||
initializedAnalytics = analytics({amplitudeToken: 'token', googleAnalytics: 'token'});
|
||||
analytics.__set__('amplitude.track', amplitudeTrack);
|
||||
analytics.__set__('ga.event', googleEvent);
|
||||
analytics.__set__('ga.transaction', googleTransaction);
|
||||
|
||||
purchaseData = {
|
||||
uuid: 'user-id',
|
||||
sku: 'paypal-checkout',
|
||||
paymentMethod: 'PayPal',
|
||||
itemPurchased: 'Gems',
|
||||
purchaseValue: 8,
|
||||
purchaseType: 'checkout',
|
||||
gift: false,
|
||||
quantity: 1
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
context('Amplitude', function() {
|
||||
|
||||
it('calls amplitude.track', function() {
|
||||
initializedAnalytics.trackPurchase(purchaseData);
|
||||
|
||||
expect(amplitudeTrack).to.be.calledOnce;
|
||||
expect(amplitudeTrack).to.be.calledWith({
|
||||
event_type: 'purchase',
|
||||
user_id: 'user-id',
|
||||
platform: 'server',
|
||||
event_properties: {
|
||||
paymentMethod: 'PayPal',
|
||||
sku: 'paypal-checkout',
|
||||
gift: false,
|
||||
itemPurchased: 'Gems',
|
||||
purchaseType: 'checkout',
|
||||
quantity: 1
|
||||
},
|
||||
revenue: 8
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Google Analytics', function() {
|
||||
|
||||
it('calls ga.event', function() {
|
||||
initializedAnalytics.trackPurchase(purchaseData);
|
||||
|
||||
expect(googleEvent).to.be.calledOnce;
|
||||
expect(googleEvent).to.be.calledWith({
|
||||
ec: 'commerce',
|
||||
ea: 'checkout',
|
||||
el: 'PayPal',
|
||||
ev: 8
|
||||
});
|
||||
});
|
||||
|
||||
it('calls ga.transaction', function() {
|
||||
initializedAnalytics.trackPurchase(purchaseData);
|
||||
|
||||
expect(googleTransaction).to.be.calledOnce;
|
||||
expect(googleTransaction).to.be.calledWith(
|
||||
'user-id',
|
||||
8
|
||||
);
|
||||
expect(googleItem).to.be.calledOnce;
|
||||
expect(googleItem).to.be.calledWith(
|
||||
8,
|
||||
1,
|
||||
'paypal-checkout',
|
||||
'Gems',
|
||||
'checkout'
|
||||
);
|
||||
});
|
||||
|
||||
it('appends gift to variation of ga.transaction.item if gift is true', function() {
|
||||
|
||||
purchaseData.gift = true;
|
||||
initializedAnalytics.trackPurchase(purchaseData);
|
||||
|
||||
expect(googleItem).to.be.calledOnce;
|
||||
expect(googleItem).to.be.calledWith(
|
||||
8,
|
||||
1,
|
||||
'paypal-checkout',
|
||||
'Gems',
|
||||
'checkout - Gift'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
'use strict'
|
||||
//Adapted from http://stackoverflow.com/questions/23785603/angularjs-testing-with-jasmine-and-mixpanel
|
||||
// @TODO: replace with an injectable mixpanel instance for testing
|
||||
|
||||
var MixpanelMock;
|
||||
|
||||
MixpanelMock = (function() {
|
||||
function MixpanelMock() {}
|
||||
|
||||
MixpanelMock.prototype.track = function() {
|
||||
return console.log("mixpanel.track", arguments);
|
||||
};
|
||||
|
||||
MixpanelMock.prototype.register_once = function() {
|
||||
return console.log("mixpanel.register_once", arguments);
|
||||
};
|
||||
|
||||
MixpanelMock.prototype.identify = function() {
|
||||
return console.log("mixpanel.identify", arguments);
|
||||
};
|
||||
|
||||
MixpanelMock.prototype.register = function() {
|
||||
return console.log("mixpanel.register", arguments);
|
||||
};
|
||||
|
||||
return MixpanelMock;
|
||||
|
||||
})();
|
||||
|
||||
window.mixpanel = new MixpanelMock();
|
||||
|
|
@ -50,15 +50,6 @@ angular.module('habitrpg')
|
|||
if($rootScope.selectedLanguage) url = url + '?lang=' + $rootScope.selectedLanguage.code;
|
||||
$http.post(url, scope.registerVals).success(function(data, status, headers, config) {
|
||||
runAuth(data.id, data.apiToken);
|
||||
if (status == 200) {
|
||||
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'});
|
||||
} else {
|
||||
Analytics.updateUser({'email':data.auth.local.email,'language':data.preferences.language});
|
||||
Analytics.track({'hitType':'event','eventCategory':'acquisition','eventAction':'register','authType':'email'});
|
||||
}
|
||||
}
|
||||
}).error(errorAlert);
|
||||
};
|
||||
|
||||
|
|
|
|||
190
website/src/analytics.js
Normal file
190
website/src/analytics.js
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
var _ = require('lodash');
|
||||
require('coffee-script'); // remove this once we've fully converted over
|
||||
var i18n = require('./i18n');
|
||||
var Content = require('../../common/script/content');
|
||||
var Amplitude = require('amplitude');
|
||||
var googleAnalytics = require('universal-analytics');
|
||||
|
||||
var ga;
|
||||
var amplitude;
|
||||
|
||||
var analytics = {
|
||||
trackPurchase: trackPurchase,
|
||||
track: track
|
||||
}
|
||||
|
||||
function init(options) {
|
||||
if(!options) { throw 'No options provided' }
|
||||
|
||||
amplitude = new Amplitude(options.amplitudeToken);
|
||||
ga = googleAnalytics(options.googleAnalytics);
|
||||
|
||||
return analytics;
|
||||
}
|
||||
|
||||
function track(eventType, data) {
|
||||
_sendDataToAmplitude(eventType, data);
|
||||
_sendDataToGoogle(eventType, data);
|
||||
}
|
||||
|
||||
function _sendDataToAmplitude(eventType, data) {
|
||||
var amplitudeData = _formatDataForAmplitude(data);
|
||||
amplitudeData.event_type = eventType;
|
||||
amplitude.track(amplitudeData);
|
||||
}
|
||||
|
||||
function _sendDataToGoogle(eventType, data) {
|
||||
var eventData = {
|
||||
ec: data.category,
|
||||
ea: eventType
|
||||
}
|
||||
|
||||
var label = _generateLabelForGoogleAnalytics(data);
|
||||
if(label) { eventData.el = label; }
|
||||
|
||||
var value = _generateValueForGoogleAnalytics(data);
|
||||
if(value) { eventData.ev = value; }
|
||||
|
||||
ga.event(eventData).send();
|
||||
}
|
||||
|
||||
function _generateLabelForGoogleAnalytics(data) {
|
||||
var label;
|
||||
var POSSIBLE_LABELS = ['gaLabel', 'itemKey'];
|
||||
|
||||
_(POSSIBLE_LABELS).each(function(key) {
|
||||
if(data[key]) {
|
||||
label = data[key];
|
||||
return false; // exit _.each early
|
||||
}
|
||||
});
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
function _generateValueForGoogleAnalytics(data) {
|
||||
var value;
|
||||
var POSSIBLE_VALUES = ['gaValue', 'gemCost', 'goldCost'];
|
||||
|
||||
_(POSSIBLE_VALUES).each(function(key) {
|
||||
if(data[key]) {
|
||||
value = data[key];
|
||||
return false; // exit _.each early
|
||||
}
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function trackPurchase(data) {
|
||||
_sendPurchaseDataToAmplitude(data);
|
||||
_sendPurchaseDataToGoogle(data);
|
||||
}
|
||||
|
||||
function _sendPurchaseDataToAmplitude(data) {
|
||||
var amplitudeData = _formatDataForAmplitude(data);
|
||||
amplitudeData.event_type = 'purchase';
|
||||
amplitudeData.revenue = data.purchaseValue;
|
||||
|
||||
amplitude.track(amplitudeData)
|
||||
}
|
||||
|
||||
function _formatDataForAmplitude(data) {
|
||||
var PROPERTIES_TO_SCRUB = ['uuid', 'user', 'purchaseValue', 'gaLabel', 'gaValue'];
|
||||
var event_properties = _.omit(data, PROPERTIES_TO_SCRUB);
|
||||
|
||||
var ampData = {
|
||||
user_id: data.uuid,
|
||||
platform: 'server',
|
||||
event_properties: event_properties
|
||||
}
|
||||
|
||||
if(data.user) {
|
||||
ampData.user_properties = _formatUserData(data.user);
|
||||
}
|
||||
|
||||
var itemName = _lookUpItemName(data.itemKey);
|
||||
if(itemName) {
|
||||
event_properties.itemName = itemName;
|
||||
}
|
||||
|
||||
return ampData;
|
||||
}
|
||||
|
||||
function _lookUpItemName(itemKey) {
|
||||
if (!itemKey) return;
|
||||
|
||||
var gear = Content.gear.flat[itemKey];
|
||||
var egg = Content.eggs[itemKey];
|
||||
var food = Content.food[itemKey];
|
||||
var hatchingPotion = Content.hatchingPotions[itemKey];
|
||||
var quest = Content.quests[itemKey];
|
||||
var spell = Content.special[itemKey];
|
||||
|
||||
var itemName;
|
||||
|
||||
if (gear) {
|
||||
itemName = gear.text();
|
||||
} else if (egg) {
|
||||
itemName = egg.text() + ' Egg';
|
||||
} else if (food) {
|
||||
itemName = food.text();
|
||||
} else if (hatchingPotion) {
|
||||
itemName = hatchingPotion.text() + " Hatching Potion";
|
||||
} else if (quest) {
|
||||
itemName = quest.text();
|
||||
} else if (spell) {
|
||||
itemName = spell.text();
|
||||
}
|
||||
|
||||
return itemName;
|
||||
}
|
||||
|
||||
function _formatUserData(user) {
|
||||
var properties = {};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
function _sendPurchaseDataToGoogle(data) {
|
||||
var label = data.paymentMethod;
|
||||
var type = data.purchaseType;
|
||||
var price = data.purchaseValue;
|
||||
var qty = data.quantity;
|
||||
var sku = data.sku;
|
||||
var itemKey = data.itemPurchased;
|
||||
var variation = type;
|
||||
if(data.gift) variation += ' - Gift';
|
||||
|
||||
var eventData = {
|
||||
ec: 'commerce',
|
||||
ea: type,
|
||||
el: label,
|
||||
ev: price
|
||||
};
|
||||
|
||||
ga.event(eventData).send();
|
||||
|
||||
ga.transaction(data.uuid, price)
|
||||
.item(price, qty, sku, itemKey, variation)
|
||||
.send();
|
||||
}
|
||||
|
||||
module.exports = init;
|
||||
|
|
@ -8,7 +8,7 @@ var nconf = require('nconf');
|
|||
var request = require('request');
|
||||
var User = require('../models/user').model;
|
||||
var EmailUnsubscription = require('../models/emailUnsubscription').model;
|
||||
var ga = require('./../utils').ga;
|
||||
var analytics = utils.analytics;
|
||||
var i18n = require('./../i18n');
|
||||
|
||||
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||
|
|
@ -110,7 +110,14 @@ api.registerUser = function(req, res, next) {
|
|||
newUser.preferences = newUser.preferences || {};
|
||||
newUser.preferences.language = req.language; // User language detected from browser, not saved
|
||||
var user = new User(newUser);
|
||||
ga.event('acquisition', 'register', 'local').send();
|
||||
|
||||
var analyticsData = {
|
||||
category: 'acquisition',
|
||||
type: 'local',
|
||||
gaLabel: 'local'
|
||||
};
|
||||
analytics.track('register', analyticsData)
|
||||
|
||||
user.save(function(err, savedUser){
|
||||
// Clean previous email preferences
|
||||
EmailUnsubscription.remove({email: savedUser.auth.local.email}, function(){
|
||||
|
|
@ -194,7 +201,12 @@ api.loginSocial = function(req, res, next) {
|
|||
cb.apply(cb, arguments);
|
||||
});
|
||||
|
||||
ga.event('acquisition', 'register', network).send();
|
||||
var analyticsData = {
|
||||
category: 'acquisition',
|
||||
type: network,
|
||||
gaLabel: network
|
||||
};
|
||||
analytics.track('register', analyticsData)
|
||||
}]
|
||||
}, function(err, results){
|
||||
if (err) return res.json(401, {err: err.toString ? err.toString() : err});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ var EmailUnsubscription = require('./../models/emailUnsubscription').model;
|
|||
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||
var api = module.exports;
|
||||
var pushNotify = require('./pushNotifications');
|
||||
var analytics = utils.analytics;
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
|
|
@ -925,6 +926,14 @@ api.questAccept = function(req, res, next) {
|
|||
// or everyone has either accepted/rejected, then we store quest key in user object.
|
||||
_.each(group.members, function(m){
|
||||
if (m == user._id) {
|
||||
var analyticsData = {
|
||||
category: 'behavior',
|
||||
owner: true,
|
||||
response: 'accept',
|
||||
gaLabel: 'accept',
|
||||
questName: key
|
||||
};
|
||||
analytics.track('quest',analyticsData);
|
||||
group.quest.members[m] = true;
|
||||
group.quest.leader = user._id;
|
||||
} else {
|
||||
|
|
@ -963,6 +972,14 @@ api.questAccept = function(req, res, next) {
|
|||
// Party member accepting the invitation
|
||||
} else {
|
||||
if (!group.quest.key) return res.json(400,{err:'No quest invitation has been sent out yet.'});
|
||||
var analyticsData = {
|
||||
category: 'behavior',
|
||||
owner: false,
|
||||
response: 'accept',
|
||||
gaLabel: 'accept',
|
||||
questName: group.quest.key
|
||||
};
|
||||
analytics.track('quest',analyticsData);
|
||||
group.quest.members[user._id] = true;
|
||||
User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false}}).exec();
|
||||
questStart(req,res,next);
|
||||
|
|
@ -974,6 +991,14 @@ api.questReject = function(req, res, next) {
|
|||
var user = res.locals.user;
|
||||
|
||||
if (!group.quest.key) return res.json(400,{err:'No quest invitation has been sent out yet.'});
|
||||
var analyticsData = {
|
||||
category: 'behavior',
|
||||
owner: false,
|
||||
response: 'reject',
|
||||
gaLabel: 'reject',
|
||||
questName: group.quest.key
|
||||
};
|
||||
analytics.track('quest',analyticsData);
|
||||
group.quest.members[user._id] = false;
|
||||
User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec();
|
||||
questStart(req,res,next);
|
||||
|
|
|
|||
|
|
@ -74,9 +74,18 @@ exports.createSubscription = function(data, cb) {
|
|||
revealMysteryItems(recipient);
|
||||
if(isProduction) {
|
||||
if (!data.gift) utils.txnEmail(data.user, 'subscription-begins');
|
||||
utils.ga.event('commerce', 'subscribe', data.paymentMethod, block.price).send();
|
||||
utils.ga.transaction(data.user._id, block.price).item(block.price, 1, data.paymentMethod.toLowerCase() + '-subscription', data.paymentMethod).send();
|
||||
utils.mixpanel.track('purchase',{'distinct_id':data.user._id,'itemPurchased':block.key,'purchaseValue':block.price})
|
||||
|
||||
var analyticsData = {
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Subscription',
|
||||
sku: data.paymentMethod.toLowerCase() + '-subscription',
|
||||
purchaseType: 'subscribe',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: !!data.gift, // coerced into a boolean
|
||||
purchaseValue: block.price
|
||||
}
|
||||
utils.analytics.trackPurchase(analyticsData);
|
||||
}
|
||||
data.user.purchased.txnCount++;
|
||||
if (data.gift){
|
||||
|
|
@ -118,7 +127,13 @@ exports.cancelSubscription = function(data, cb) {
|
|||
|
||||
data.user.save(cb);
|
||||
utils.txnEmail(data.user, 'cancel-subscription');
|
||||
utils.ga.event('commerce', 'unsubscribe', data.paymentMethod).send();
|
||||
var analyticsData = {
|
||||
uuid: data.user._id,
|
||||
gaCategory: 'commerce',
|
||||
gaLabel: data.paymentMethod,
|
||||
paymentMethod: data.paymentMethod
|
||||
}
|
||||
utils.analytics.track('unsubscribe', analyticsData);
|
||||
}
|
||||
|
||||
exports.buyGems = function(data, cb) {
|
||||
|
|
@ -127,11 +142,20 @@ exports.buyGems = function(data, cb) {
|
|||
data.user.purchased.txnCount++;
|
||||
if(isProduction) {
|
||||
if (!data.gift) utils.txnEmail(data.user, 'donation');
|
||||
utils.ga.event('commerce', 'checkout', data.paymentMethod, amt).send();
|
||||
utils.mixpanel.track('purchase',{'distinct_id':data.user._id,'itemPurchased':'Gems','purchaseValue':amt})
|
||||
//TODO ga.transaction to reflect whether this is gift or self-purchase
|
||||
utils.ga.transaction(data.user._id, amt).item(amt, 1, data.paymentMethod.toLowerCase() + "-checkout", "Gems > " + data.paymentMethod).send();
|
||||
|
||||
var analyticsData = {
|
||||
uuid: data.user._id,
|
||||
itemPurchased: 'Gems',
|
||||
sku: data.paymentMethod.toLowerCase() + '-checkout',
|
||||
purchaseType: 'checkout',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: !!data.gift, // coerced into a boolean
|
||||
purchaseValue: amt
|
||||
}
|
||||
utils.analytics.trackPurchase(analyticsData);
|
||||
}
|
||||
|
||||
if (data.gift){
|
||||
var byUsername = utils.getUserInfo(data.user, ['name']).name;
|
||||
var gemAmount = data.gift.gems.amount || 20;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ var async = require('async');
|
|||
var shared = require('../../../common');
|
||||
var User = require('./../models/user').model;
|
||||
var utils = require('./../utils');
|
||||
var ga = utils.ga;
|
||||
var analytics = utils.analytics;
|
||||
var Group = require('./../models/group').model;
|
||||
var Challenge = require('./../models/challenge').model;
|
||||
var moment = require('moment');
|
||||
|
|
@ -102,7 +102,7 @@ api.score = function(req, res, next) {
|
|||
|
||||
if (task.type === 'daily' || task.type === 'todo')
|
||||
task.completed = direction === 'up';
|
||||
|
||||
|
||||
task = user.ops.addTask({body:task});
|
||||
}
|
||||
var delta = user.ops.score({params:{id:task.id, direction:direction}, language: req.language});
|
||||
|
|
@ -336,7 +336,7 @@ api.update = function(req, res, next) {
|
|||
|
||||
api.cron = function(req, res, next) {
|
||||
var user = res.locals.user,
|
||||
progress = user.fns.cron({ga:ga, mixpanel:utils.mixpanel}),
|
||||
progress = user.fns.cron({analytics:utils.analytics}),
|
||||
ranCron = user.isModified(),
|
||||
quest = shared.content.quests[user.party.quest.key];
|
||||
|
||||
|
|
@ -535,7 +535,7 @@ _.each(shared.wrap({}).ops, function(op,k){
|
|||
if (err) return next(err);
|
||||
res.json(200,response);
|
||||
})
|
||||
}, ga);
|
||||
}, analytics);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ var request = require('request');
|
|||
// Set when utils.setupConfig is run
|
||||
var isProd, baseUrl;
|
||||
|
||||
module.exports.ga = undefined; // set Google Analytics on nconf init
|
||||
|
||||
module.exports.sendEmail = function(mailData) {
|
||||
var smtpTransport = nodemailer.createTransport("SMTP",{
|
||||
service: nconf.get('SMTP_SERVICE'),
|
||||
|
|
@ -178,12 +176,15 @@ module.exports.setupConfig = function(){
|
|||
isProd = nconf.get('NODE_ENV') === 'production';
|
||||
baseUrl = nconf.get('BASE_URL');
|
||||
|
||||
module.exports.ga = require('universal-analytics')(nconf.get('GA_ID'));
|
||||
var analytics = isProd && require('./analytics');
|
||||
var analyticsTokens = {
|
||||
amplitudeToken: nconf.get('AMPLITUDE_KEY'),
|
||||
googleAnalytics: nconf.get('GA_ID')
|
||||
}
|
||||
|
||||
var mixpanel = isProd && require('mixpanel');
|
||||
module.exports.mixpanel = mixpanel
|
||||
? mixpanel.init(nconf.get('MP_ID'))
|
||||
: { track: function() {} };
|
||||
module.exports.analytics = analytics
|
||||
? analytics(analyticsTokens)
|
||||
: { track: function() { }, trackPurchase: function() { } };
|
||||
};
|
||||
|
||||
var algorithm = 'aes-256-ctr';
|
||||
|
|
|
|||
Loading…
Reference in a new issue