2015-02-10 11:34:11 -08:00
// A/B test helper functions
// Loaded from ab*.js ab test result scripts
2015-02-11 11:07:32 -08:00
// Main APIs are getFunnelData() and printFunnelData()
2015-02-10 11:34:11 -08:00
// TODO: use levelSlugs in query if available
2015-02-10 13:16:09 -08:00
// TODO: Stop looking up testGroupNumber when test group data is available in analytics.log.events
2015-02-10 11:34:11 -08:00
// TODO: These are super slow, need to aggregate into analytics.perdays collection
2015-02-11 11:07:32 -08:00
var browserCountPrintThreshold = 1000 ;
2015-02-10 11:34:11 -08:00
var analyticsStringCache = { } ;
var analyticsStringIDCache = { } ;
// *** Helper functions ***
function log ( str ) {
print ( new Date ( ) . toISOString ( ) + " " + str ) ;
}
function objectIdWithTimestamp ( timestamp ) {
// Convert string date to Date object (otherwise assume timestamp is a date)
if ( typeof ( timestamp ) == 'string' ) timestamp = new Date ( timestamp ) ;
// Convert date object to hex seconds since Unix epoch
var hexSeconds = Math . floor ( timestamp / 1000 ) . toString ( 16 ) ;
// Create an ObjectId with that hex timestamp
var constructedObjectId = ObjectId ( hexSeconds + "0000000000000000" ) ;
return constructedObjectId
}
function getAnalyticsString ( strID ) {
if ( analyticsStringCache [ strID ] ) return analyticsStringCache [ strID ] ;
var doc = db [ 'analytics.strings' ] . findOne ( { _id : strID } ) ;
if ( doc ) {
analyticsStringCache [ strID ] = doc . v ;
return analyticsStringCache [ strID ] ;
}
throw new Error ( "ERROR: Did not find analytics.strings insert for: " + str ) ;
}
function getAnalyticsStringID ( str ) {
if ( analyticsStringIDCache [ str ] ) return analyticsStringIDCache [ str ] ;
var doc = db [ 'analytics.strings' ] . findOne ( { v : str } ) ;
if ( doc ) {
analyticsStringIDCache [ str ] = doc . _id ;
return analyticsStringIDCache [ str ] ;
}
throw new Error ( "ERROR: Did not find analytics.strings insert for: " + str ) ;
}
function getFunnelData ( startDay , eventFunnel , testGroupFn , levelSlugs ) {
if ( ! startDay || ! eventFunnel || eventFunnel . length === 0 || ! testGroupFn ) return { } ;
// log('getFunnelData:');
// log(startDay);
// log(eventFunnel);
var startObj = objectIdWithTimestamp ( ISODate ( startDay + "T00:00:00.000Z" ) ) ;
var queryParams = { $and : [ { _id : { $gte : startObj } } , { "event" : { $in : eventFunnel } } ] } ;
var cursor = db [ 'analytics.log.events' ] . find ( queryParams ) ;
2015-02-11 11:07:32 -08:00
log ( "Fetching events.." ) ;
2015-02-10 11:34:11 -08:00
// Map ordering: level, user, event, day
var levelUserEventMap = { } ;
2015-02-11 11:07:32 -08:00
var levelSessions = [ ] ;
2015-02-10 11:34:11 -08:00
var users = [ ] ;
while ( cursor . hasNext ( ) ) {
var doc = cursor . next ( ) ;
var created = doc . _id . getTimestamp ( ) . toISOString ( ) ;
var day = created . substring ( 0 , 10 ) ;
var event = doc . event ;
var properties = doc . properties ;
var user = doc . user . valueOf ( ) ;
2015-02-11 11:07:32 -08:00
var level = 'n/a' ;
var ls = null ;
2015-02-10 11:34:11 -08:00
// TODO: Switch to properties.levelID for 'Saw Victory'
if ( event === 'Saw Victory' && properties . level ) level = properties . level . toLowerCase ( ) . replace ( / /g , '-' ) ;
else if ( properties . levelID ) level = properties . levelID
if ( levelSlugs && levelSlugs . indexOf ( level ) < 0 ) continue ;
2015-02-11 11:07:32 -08:00
if ( properties && properties . ls ) {
ls = properties . ls . valueOf ( ) ;
levelSessions . push ( properties . ls ) ;
}
2015-02-10 11:34:11 -08:00
users . push ( ObjectId ( user ) ) ;
if ( ! levelUserEventMap [ level ] ) levelUserEventMap [ level ] = { } ;
if ( ! levelUserEventMap [ level ] [ user ] ) levelUserEventMap [ level ] [ user ] = { } ;
if ( ! levelUserEventMap [ level ] [ user ] [ event ]
2015-02-11 11:07:32 -08:00
|| levelUserEventMap [ level ] [ user ] [ event ] [ 'day' ] . localeCompare ( day ) > 0 ) {
levelUserEventMap [ level ] [ user ] [ event ] = { day : day } ;
if ( ls ) {
levelUserEventMap [ level ] [ user ] [ event ] [ 'ls' ] = ls ;
}
2015-02-10 11:34:11 -08:00
}
}
// printjson(levelUserEventMap);
// printjson(users);
2015-02-11 11:07:32 -08:00
log ( "Fetching users.." ) ;
2015-02-10 11:34:11 -08:00
var userGroupMap = { } ;
cursor = db [ 'users' ] . find ( { _id : { $in : users } } ) ;
while ( cursor . hasNext ( ) ) {
var doc = cursor . next ( ) ;
var user = doc . _id . valueOf ( ) ;
userGroupMap [ user ] = testGroupFn ( doc . testGroupNumber ) ;
}
// printjson(userGroupMap);
2015-02-11 11:07:32 -08:00
log ( "Fetching level sessions.." ) ;
var lsBrowserMap = { } ;
var userBrowserMap = { } ;
cursor = db [ 'level.sessions' ] . find ( { _id : { $in : levelSessions } } ) ;
while ( cursor . hasNext ( ) ) {
var doc = cursor . next ( ) ;
var user = doc . _id . valueOf ( ) ;
var browser = doc . browser ;
var browserInfo = '' ;
if ( browser && browser . platform ) {
browserInfo += browser . platform ;
}
if ( browser && browser . name ) {
browserInfo += browser . name ;
}
if ( browserInfo . length > 0 ) {
lsBrowserMap [ doc . _id . valueOf ( ) ] = browserInfo ;
userBrowserMap [ user ] = browserInfo ;
}
}
// printjson(lsBrowserMap);
log ( "Mapping data.." ) ;
2015-02-10 11:34:11 -08:00
// Data: level, day, event
2015-02-11 11:07:32 -08:00
var levelDayGroupBrowserEventMap = { } ;
2015-02-10 11:34:11 -08:00
for ( level in levelUserEventMap ) {
for ( user in levelUserEventMap [ level ] ) {
var group = userGroupMap [ user ] ;
2015-02-11 11:07:32 -08:00
var browser = userBrowserMap [ user ] || 'unknown' ;
2015-02-10 11:34:11 -08:00
// Find first event date
var funnelStartDay = null ;
2015-02-11 11:07:32 -08:00
var funnelStartBrowser = null ;
2015-02-10 11:34:11 -08:00
for ( event in levelUserEventMap [ level ] [ user ] ) {
2015-02-11 11:07:32 -08:00
var day = levelUserEventMap [ level ] [ user ] [ event ] [ 'day' ] ;
var ls = levelUserEventMap [ level ] [ user ] [ event ] [ 'ls' ] ;
if ( lsBrowserMap [ ls ] ) {
browser = lsBrowserMap [ ls ] ;
}
if ( ! levelDayGroupBrowserEventMap [ level ] ) levelDayGroupBrowserEventMap [ level ] = { } ;
if ( ! levelDayGroupBrowserEventMap [ level ] [ day ] ) levelDayGroupBrowserEventMap [ level ] [ day ] = { } ;
if ( ! levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] ) levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] = { } ;
if ( ! levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] [ browser ] ) {
levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] [ browser ] = { } ;
}
if ( ! levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] [ browser ] [ event ] ) {
levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] [ browser ] [ event ] = 0 ;
}
2015-02-10 11:34:11 -08:00
if ( eventFunnel [ 0 ] === event ) {
// First event gets attributed to current date
2015-02-11 11:07:32 -08:00
levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] [ browser ] [ event ] ++ ;
2015-02-10 11:34:11 -08:00
funnelStartDay = day ;
2015-02-11 11:07:32 -08:00
funnelStartBrowser = browser ;
2015-02-10 11:34:11 -08:00
break ;
}
}
if ( funnelStartDay ) {
// Add remaining funnel steps/events to first step's date
for ( event in levelUserEventMap [ level ] [ user ] ) {
2015-02-11 11:07:32 -08:00
if ( ! levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] ) {
levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] = { } ;
}
if ( ! levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] [ event ] ) {
levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] [ event ] = 0 ;
}
if ( eventFunnel [ 0 ] !== event ) {
levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] [ event ] ++ ;
2015-02-10 11:34:11 -08:00
}
}
// Zero remaining funnel events
for ( var i = 1 ; i < eventFunnel . length ; i ++ ) {
var event = eventFunnel [ i ] ;
2015-02-11 11:07:32 -08:00
if ( ! levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] [ event ] ) {
levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] [ event ] = 0 ;
}
if ( ! levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] [ event ] ) {
levelDayGroupBrowserEventMap [ level ] [ funnelStartDay ] [ group ] [ funnelStartBrowser ] [ event ] = 0 ;
2015-02-10 11:34:11 -08:00
}
}
}
// Else no start event in this date range
}
}
2015-02-11 11:07:32 -08:00
// printjson(levelDayGroupBrowserEventMap);
2015-02-10 11:34:11 -08:00
2015-02-11 11:07:32 -08:00
log ( "Building results.." ) ;
2015-02-10 11:34:11 -08:00
var funnelData = [ ] ;
2015-02-11 11:07:32 -08:00
for ( level in levelDayGroupBrowserEventMap ) {
for ( day in levelDayGroupBrowserEventMap [ level ] ) {
for ( group in levelDayGroupBrowserEventMap [ level ] [ day ] ) {
for ( browser in levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] ) {
var started = 0 ;
var finished = 0 ;
for ( event in levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] [ browser ] ) {
if ( event === eventFunnel [ 0 ] ) {
started = levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] [ browser ] [ event ] ;
}
else if ( event === eventFunnel [ eventFunnel . length - 1 ] ) {
finished = levelDayGroupBrowserEventMap [ level ] [ day ] [ group ] [ browser ] [ event ] ;
}
2015-02-10 11:34:11 -08:00
}
2015-02-11 11:07:32 -08:00
funnelData . push ( {
level : level ,
day : day ,
group : group ,
browser : browser ,
started : started ,
finished : finished
} ) ;
2015-02-10 11:34:11 -08:00
}
}
}
}
2015-02-11 11:07:32 -08:00
log ( "Sorting results.." ) ;
2015-02-10 11:34:11 -08:00
funnelData . sort ( function ( a , b ) {
if ( a . level !== b . level ) {
return a . level < b . level ? - 1 : 1 ;
}
else if ( a . day !== b . day ) {
return a . day < b . day ? - 1 : 1 ;
}
2015-02-11 11:07:32 -08:00
else if ( a . browser !== b . browser ) {
return a . browser < b . browser ? - 1 : 1 ;
}
2015-02-10 11:34:11 -08:00
return a . group < b . group ? - 1 : 1 ;
} ) ;
return funnelData ;
}
2015-02-11 11:07:32 -08:00
function printFunnelData ( funnelData , printFn ) {
log ( "Day\t\tGroup\t\tStarted\tFinished\tCompletion Rate" ) ;
var levelBrowserGroupCounts = { } ;
var levelGroupCounts = { } ;
var groupCounts = { } ;
for ( var i = 0 ; i < funnelData . length ; i ++ ) {
var level = funnelData [ i ] . level ;
var day = funnelData [ i ] . day ;
var browser = funnelData [ i ] . browser ;
var group = funnelData [ i ] . group ;
var started = funnelData [ i ] . started ;
var finished = funnelData [ i ] . finished ;
var rate = started > 0 ? finished / started * 100 : 0.0 ;
printFn ( day , level , browser , group , started , finished , rate ) ;
if ( ! levelBrowserGroupCounts [ level ] ) levelBrowserGroupCounts [ level ] = { } ;
if ( ! levelBrowserGroupCounts [ level ] [ browser ] ) levelBrowserGroupCounts [ level ] [ browser ] = { } ;
if ( ! levelBrowserGroupCounts [ level ] [ browser ] [ group ] ) {
levelBrowserGroupCounts [ level ] [ browser ] [ group ] = { started : 0 , finished : 0 } ;
}
levelBrowserGroupCounts [ level ] [ browser ] [ group ] [ 'started' ] += started ;
levelBrowserGroupCounts [ level ] [ browser ] [ group ] [ 'finished' ] += finished ;
if ( ! levelGroupCounts [ level ] ) levelGroupCounts [ level ] = { } ;
if ( ! levelGroupCounts [ level ] [ group ] ) levelGroupCounts [ level ] [ group ] = { started : 0 , finished : 0 } ;
levelGroupCounts [ level ] [ group ] [ 'started' ] += started ;
levelGroupCounts [ level ] [ group ] [ 'finished' ] += finished ;
if ( ! groupCounts [ group ] ) groupCounts [ group ] = { started : 0 , finished : 0 } ;
groupCounts [ group ] [ 'started' ] += started ;
groupCounts [ group ] [ 'finished' ] += finished ;
}
log ( "" ) ;
log ( "Browser totals:" ) ;
for ( level in levelBrowserGroupCounts ) {
for ( browser in levelBrowserGroupCounts [ level ] ) {
for ( group in levelBrowserGroupCounts [ level ] [ browser ] ) {
var started = levelBrowserGroupCounts [ level ] [ browser ] [ group ] . started ;
if ( started < browserCountPrintThreshold ) continue ;
var finished = levelBrowserGroupCounts [ level ] [ browser ] [ group ] . finished ;
var rate = started > 0 ? finished / started * 100 : 0.0 ;
printFn ( null , level , browser , group , started , finished , rate ) ;
}
}
}
log ( "" ) ;
log ( "Level totals:" ) ;
for ( level in levelGroupCounts ) {
for ( group in levelGroupCounts [ level ] ) {
var started = levelGroupCounts [ level ] [ group ] . started ;
var finished = levelGroupCounts [ level ] [ group ] . finished ;
var rate = started > 0 ? finished / started * 100 : 0.0 ;
printFn ( null , level , null , group , started , finished , rate ) ;
}
}
log ( "" ) ;
log ( "Group totals:" ) ;
for ( group in groupCounts ) {
var started = groupCounts [ group ] . started ;
var finished = groupCounts [ group ] . finished ;
var rate = started > 0 ? finished / started * 100 : 0.0 ;
printFn ( null , null , null , group , started , finished , rate ) ;
}
}