mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 23:58:02 -05:00
Add shortened fields to analytics.log.event
We’ll remove the old long fields after we’ve got enough data to switch over our analytics queries without complication.
This commit is contained in:
parent
865ae66b5c
commit
c5977c00fe
6 changed files with 125 additions and 19 deletions
|
@ -1,5 +1,5 @@
|
||||||
{me} = require 'core/auth'
|
{me} = require 'core/auth'
|
||||||
AnalyticsLogEvent = require 'models/AnalyticsLogEvent'
|
SuperModel = require 'models/SuperModel'
|
||||||
|
|
||||||
debugAnalytics = false
|
debugAnalytics = false
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ module.exports = class Tracker
|
||||||
window.tracker = @
|
window.tracker = @
|
||||||
@isProduction = document.location.href.search('codecombat.com') isnt -1
|
@isProduction = document.location.href.search('codecombat.com') isnt -1
|
||||||
@identify()
|
@identify()
|
||||||
|
@supermodel = new SuperModel()
|
||||||
|
|
||||||
identify: (traits) ->
|
identify: (traits) ->
|
||||||
console.log 'Would identify', traits if debugAnalytics
|
console.log 'Would identify', traits if debugAnalytics
|
||||||
|
@ -61,19 +62,7 @@ module.exports = class Tracker
|
||||||
# https://segment.com/docs/integrations/mixpanel/
|
# https://segment.com/docs/integrations/mixpanel/
|
||||||
properties = properties or {}
|
properties = properties or {}
|
||||||
|
|
||||||
# Log internally
|
@trackEventInternal action, _.cloneDeep properties
|
||||||
# Skipping heavily logged actions we don't use internally
|
|
||||||
unless action in ['Simulator Result', 'Started Level Load', 'Finished Level Load']
|
|
||||||
# Trimming properties we don't use internally
|
|
||||||
# TODO: delete internalProperites.level for 'Saw Victory' after 2/8/15. Should be using levelID instead.
|
|
||||||
internalProperties = _.cloneDeep properties
|
|
||||||
if action in ['Clicked Level', 'Inventory Play', 'Heard Sprite', 'Started Level', 'Saw Victory', 'Click Play', 'Choose Inventory']
|
|
||||||
delete internalProperties.category
|
|
||||||
delete internalProperties.label
|
|
||||||
|
|
||||||
console.log 'Tracking internal analytics event:', action, internalProperties, includeIntegrations if debugAnalytics
|
|
||||||
event = new AnalyticsLogEvent event: action, properties: internalProperties
|
|
||||||
event.save()
|
|
||||||
|
|
||||||
console.log 'Would track analytics event:', action, properties, includeIntegrations if debugAnalytics
|
console.log 'Would track analytics event:', action, properties, includeIntegrations if debugAnalytics
|
||||||
return unless me and @isProduction and analytics? and not me.isAdmin()
|
return unless me and @isProduction and analytics? and not me.isAdmin()
|
||||||
|
@ -85,6 +74,22 @@ module.exports = class Tracker
|
||||||
context.integrations[integration] = true
|
context.integrations[integration] = true
|
||||||
analytics?.track action, properties, context
|
analytics?.track action, properties, context
|
||||||
|
|
||||||
|
trackEventInternal: (event, properties) =>
|
||||||
|
# Skipping heavily logged actions we don't use internally
|
||||||
|
unless event in ['Simulator Result', 'Started Level Load', 'Finished Level Load']
|
||||||
|
# Trimming properties we don't use internally
|
||||||
|
# TODO: delete internalProperites.level for 'Saw Victory' after 2/8/15. Should be using levelID instead.
|
||||||
|
if event in ['Clicked Level', 'Inventory Play', 'Heard Sprite', 'Started Level', 'Saw Victory', 'Click Play', 'Choose Inventory', 'Loaded World Map', 'Homepage Loaded']
|
||||||
|
delete properties.category
|
||||||
|
delete properties.label
|
||||||
|
|
||||||
|
console.log 'Tracking internal analytics event:', event, properties if debugAnalytics
|
||||||
|
request = @supermodel.addRequestResource 'log_event', {
|
||||||
|
url: '/db/analytics_log_event/-/log_event'
|
||||||
|
data: {event: event, properties: properties}
|
||||||
|
method: 'POST'
|
||||||
|
}, 0
|
||||||
|
request.load()
|
||||||
|
|
||||||
trackTiming: (duration, category, variable, label, samplePercentage=5) ->
|
trackTiming: (duration, category, variable, label, samplePercentage=5) ->
|
||||||
# https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingTiming
|
# https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingTiming
|
||||||
|
|
|
@ -220,8 +220,8 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
||||||
@notifyScriptStateChanged()
|
@notifyScriptStateChanged()
|
||||||
@scriptInProgress = true
|
@scriptInProgress = true
|
||||||
@currentTimeouts = []
|
@currentTimeouts = []
|
||||||
scriptLabel = "#{@levelID}: #{nextNoteGroup.scriptID} - #{nextNoteGroup.name}"
|
scriptLabel = "#{nextNoteGroup.scriptID} - #{nextNoteGroup.name}"
|
||||||
application.tracker?.trackEvent 'Script Started', {label: scriptLabel}, ['Google Analytics']
|
application.tracker?.trackEvent 'Script Started', {levelID: @levelID, label: scriptLabel}, ['Google Analytics']
|
||||||
console.debug "SCRIPT: Starting note group '#{nextNoteGroup.name}'" if @debugScripts
|
console.debug "SCRIPT: Starting note group '#{nextNoteGroup.name}'" if @debugScripts
|
||||||
for module in nextNoteGroup.modules
|
for module in nextNoteGroup.modules
|
||||||
@processNote(note, nextNoteGroup) for note in module.startNotes()
|
@processNote(note, nextNoteGroup) for note in module.startNotes()
|
||||||
|
@ -283,8 +283,8 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
||||||
return if @ending # kill infinite loops right here
|
return if @ending # kill infinite loops right here
|
||||||
@ending = true
|
@ending = true
|
||||||
return unless @currentNoteGroup?
|
return unless @currentNoteGroup?
|
||||||
scriptLabel = "#{@levelID}: #{@currentNoteGroup.scriptID} - #{@currentNoteGroup.name}"
|
scriptLabel = "#{@currentNoteGroup.scriptID} - #{@currentNoteGroup.name}"
|
||||||
application.tracker?.trackEvent 'Script Ended', {label: scriptLabel}, ['Google Analytics']
|
application.tracker?.trackEvent 'Script Ended', {levelID: @levelID, label: scriptLabel}, ['Google Analytics']
|
||||||
console.debug "SCRIPT: Ending note group '#{@currentNoteGroup.name}'" if @debugScripts
|
console.debug "SCRIPT: Ending note group '#{@currentNoteGroup.name}'" if @debugScripts
|
||||||
clearTimeout(timeout) for timeout in @currentTimeouts
|
clearTimeout(timeout) for timeout in @currentTimeouts
|
||||||
for module in @currentNoteGroup.modules
|
for module in @currentNoteGroup.modules
|
||||||
|
|
|
@ -6,6 +6,11 @@ AnalyticsLogEventSchema = c.object {
|
||||||
}
|
}
|
||||||
|
|
||||||
_.extend AnalyticsLogEventSchema.properties,
|
_.extend AnalyticsLogEventSchema.properties,
|
||||||
|
u: c.objectId(links: [{rel: 'extra', href: '/db/user/{($)}'}])
|
||||||
|
e: {type: 'integer'}
|
||||||
|
p: {type: 'object'}
|
||||||
|
|
||||||
|
# TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
|
||||||
user: c.objectId(links: [{rel: 'extra', href: '/db/user/{($)}'}])
|
user: c.objectId(links: [{rel: 'extra', href: '/db/user/{($)}'}])
|
||||||
event: {type: 'string'}
|
event: {type: 'string'}
|
||||||
properties: {type: 'object'}
|
properties: {type: 'object'}
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
mongoose = require 'mongoose'
|
mongoose = require 'mongoose'
|
||||||
plugins = require '../plugins/plugins'
|
plugins = require '../plugins/plugins'
|
||||||
|
|
||||||
AnalyticsLogEventSchema = new mongoose.Schema({}, {strict: false})
|
AnalyticsLogEventSchema = new mongoose.Schema({
|
||||||
|
u: mongoose.Schema.Types.ObjectId
|
||||||
|
e: Number # event analytics.string ID
|
||||||
|
p: mongoose.Schema.Types.Mixed
|
||||||
|
|
||||||
|
# TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
|
||||||
|
user: mongoose.Schema.Types.ObjectId
|
||||||
|
event: String
|
||||||
|
properties: mongoose.Schema.Types.Mixed
|
||||||
|
}, {strict: false})
|
||||||
AnalyticsLogEventSchema.index({event: 1, _id: 1})
|
AnalyticsLogEventSchema.index({event: 1, _id: 1})
|
||||||
|
|
||||||
module.exports = AnalyticsLogEvent = mongoose.model('analytics.log.event', AnalyticsLogEventSchema)
|
module.exports = AnalyticsLogEvent = mongoose.model('analytics.log.event', AnalyticsLogEventSchema)
|
||||||
|
|
|
@ -9,6 +9,8 @@ class AnalyticsLogEventHandler extends Handler
|
||||||
modelClass: AnalyticsLogEvent
|
modelClass: AnalyticsLogEvent
|
||||||
jsonSchema: require '../../app/schemas/models/analytics_log_event'
|
jsonSchema: require '../../app/schemas/models/analytics_log_event'
|
||||||
editableProperties: [
|
editableProperties: [
|
||||||
|
'e'
|
||||||
|
'p'
|
||||||
'event'
|
'event'
|
||||||
'properties'
|
'properties'
|
||||||
]
|
]
|
||||||
|
@ -18,15 +20,77 @@ class AnalyticsLogEventHandler extends Handler
|
||||||
|
|
||||||
makeNewInstance: (req) ->
|
makeNewInstance: (req) ->
|
||||||
instance = super(req)
|
instance = super(req)
|
||||||
|
instance.set('u', req.user._id)
|
||||||
|
# TODO: Remove 'user' after we stop querying for it (probably 30 days, ~2/16/15)
|
||||||
instance.set('user', req.user._id)
|
instance.set('user', req.user._id)
|
||||||
instance
|
instance
|
||||||
|
|
||||||
getByRelationship: (req, res, args...) ->
|
getByRelationship: (req, res, args...) ->
|
||||||
|
return @logEvent(req, res) if args[1] is 'log_event'
|
||||||
# TODO: Remove these APIs
|
# TODO: Remove these APIs
|
||||||
# return @getLevelCompletionsBySlug(req, res) if args[1] is 'level_completions'
|
# return @getLevelCompletionsBySlug(req, res) if args[1] is 'level_completions'
|
||||||
# return @getCampaignCompletionsBySlug(req, res) if args[1] is 'campaign_completions'
|
# return @getCampaignCompletionsBySlug(req, res) if args[1] is 'campaign_completions'
|
||||||
super(arguments...)
|
super(arguments...)
|
||||||
|
|
||||||
|
logEvent: (req, res) ->
|
||||||
|
# Converts strings to string IDs where possible, and logs the event
|
||||||
|
user = req.user._id
|
||||||
|
event = req.query.event or req.body.event
|
||||||
|
properties = req.query.properties or req.body.properties
|
||||||
|
@sendSuccess res # Return request immediately
|
||||||
|
|
||||||
|
saveDoc = (eventID, slimProperties) ->
|
||||||
|
doc = new AnalyticsLogEvent
|
||||||
|
u: user
|
||||||
|
e: eventID
|
||||||
|
p: slimProperties
|
||||||
|
# TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
|
||||||
|
user: user
|
||||||
|
event: event
|
||||||
|
properties: properties
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
utils.getAnalyticsStringID event, (eventID) ->
|
||||||
|
if eventID > 0
|
||||||
|
# TODO: properties slimming is pretty ugly
|
||||||
|
slimProperties = _.cloneDeep properties
|
||||||
|
if event is 'Saw Victory'
|
||||||
|
delete slimProperties.level
|
||||||
|
if slimProperties.levelID?
|
||||||
|
# levelID: string => l: string ID
|
||||||
|
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
|
||||||
|
if levelStringID > 0
|
||||||
|
delete slimProperties.levelID
|
||||||
|
slimProperties.l = levelStringID
|
||||||
|
saveDoc eventID, slimProperties
|
||||||
|
return
|
||||||
|
else if event is 'Started Level'
|
||||||
|
if slimProperties.levelID?
|
||||||
|
# levelID: string => l: string ID
|
||||||
|
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
|
||||||
|
if levelStringID > 0
|
||||||
|
delete slimProperties.levelID
|
||||||
|
slimProperties.l = levelStringID
|
||||||
|
saveDoc eventID, slimProperties
|
||||||
|
return
|
||||||
|
else if event in ['Script Started', 'Script Ended']
|
||||||
|
if slimProperties.levelID?
|
||||||
|
# levelID: string => l: string ID
|
||||||
|
# label: string => lb: string ID
|
||||||
|
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
|
||||||
|
if levelStringID > 0
|
||||||
|
delete slimProperties.levelID
|
||||||
|
slimProperties.l = levelStringID
|
||||||
|
utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
|
||||||
|
if labelStringID > 0
|
||||||
|
delete slimProperties.label
|
||||||
|
slimProperties.lb = labelStringID
|
||||||
|
saveDoc eventID, slimProperties
|
||||||
|
return
|
||||||
|
saveDoc eventID, slimProperties
|
||||||
|
else
|
||||||
|
log.warn "Unable to get analytics string ID for " + event
|
||||||
|
|
||||||
getLevelCompletionsBySlug: (req, res) ->
|
getLevelCompletionsBySlug: (req, res) ->
|
||||||
# Returns an array of per-day level starts and finishes
|
# Returns an array of per-day level starts and finishes
|
||||||
# Parameters:
|
# Parameters:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
AnalyticsString = require '../analytics/AnalyticsString'
|
||||||
mongoose = require 'mongoose'
|
mongoose = require 'mongoose'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
|
@ -13,3 +14,25 @@ module.exports =
|
||||||
hexSeconds = Math.floor(timestamp/1000).toString(16)
|
hexSeconds = Math.floor(timestamp/1000).toString(16)
|
||||||
# Create an ObjectId with that hex timestamp
|
# Create an ObjectId with that hex timestamp
|
||||||
mongoose.Types.ObjectId(hexSeconds + "0000000000000000")
|
mongoose.Types.ObjectId(hexSeconds + "0000000000000000")
|
||||||
|
getAnalyticsStringID: (str, callback) ->
|
||||||
|
@analyticsStringCache ?= {}
|
||||||
|
return callback @analyticsStringCache[str] if @analyticsStringCache[str]
|
||||||
|
|
||||||
|
insertString = =>
|
||||||
|
# http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/#auto-increment-optimistic-loop
|
||||||
|
AnalyticsString.find({}, {_id: 1}).sort({_id: -1}).limit(1).exec (err, documents) =>
|
||||||
|
if err? then return callback -1
|
||||||
|
seq = if documents.length > 0 then documents[0]._id + 1 else 1
|
||||||
|
doc = new AnalyticsString _id: seq, v: str
|
||||||
|
doc.save (err) =>
|
||||||
|
if err? then return callback -1
|
||||||
|
@analyticsStringCache[str] = seq
|
||||||
|
callback seq
|
||||||
|
|
||||||
|
# Find existing string
|
||||||
|
AnalyticsString.findOne(v: str).exec (err, document) =>
|
||||||
|
if err? then return callback -1
|
||||||
|
if document
|
||||||
|
@analyticsStringCache[str] = document._id
|
||||||
|
return callback @analyticsStringCache[str]
|
||||||
|
insertString()
|
||||||
|
|
Loading…
Reference in a new issue