2015-02-10 11:34:11 -08:00
// A/B test helper functions
// Loaded from ab*.js ab test result scripts
// Main API is getFunnelData() which returns per-day funnel completion rates
// 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
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 ) ;
// Map ordering: level, user, event, day
var levelUserEventMap = { } ;
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 ( ) ;
var level ;
// 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
else level = 'n/a'
if ( levelSlugs && levelSlugs . indexOf ( level ) < 0 ) continue ;
users . push ( ObjectId ( user ) ) ;
if ( ! levelUserEventMap [ level ] ) levelUserEventMap [ level ] = { } ;
if ( ! levelUserEventMap [ level ] [ user ] ) levelUserEventMap [ level ] [ user ] = { } ;
if ( ! levelUserEventMap [ level ] [ user ] [ event ]
|| levelUserEventMap [ level ] [ user ] [ event ] . localeCompare ( day ) > 0 ) {
levelUserEventMap [ level ] [ user ] [ event ] = day ;
}
}
// printjson(levelUserEventMap);
// printjson(users);
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);
// Data: level, day, event
var levelDayGroupEventMap = { } ;
for ( level in levelUserEventMap ) {
for ( user in levelUserEventMap [ level ] ) {
var group = userGroupMap [ user ] ;
// Find first event date
var funnelStartDay = null ;
for ( event in levelUserEventMap [ level ] [ user ] ) {
var day = levelUserEventMap [ level ] [ user ] [ event ] ;
if ( ! levelDayGroupEventMap [ level ] ) levelDayGroupEventMap [ level ] = { } ;
if ( ! levelDayGroupEventMap [ level ] [ day ] ) levelDayGroupEventMap [ level ] [ day ] = { } ;
if ( ! levelDayGroupEventMap [ level ] [ day ] [ group ] ) levelDayGroupEventMap [ level ] [ day ] [ group ] = { } ;
if ( ! levelDayGroupEventMap [ level ] [ day ] [ group ] [ event ] ) levelDayGroupEventMap [ level ] [ day ] [ group ] [ event ] = 0 ;
if ( eventFunnel [ 0 ] === event ) {
// First event gets attributed to current date
levelDayGroupEventMap [ level ] [ day ] [ group ] [ event ] ++ ;
funnelStartDay = day ;
break ;
}
}
if ( funnelStartDay ) {
if ( ! levelDayGroupEventMap [ level ] [ funnelStartDay ] [ group ] ) {
levelDayGroupEventMap [ level ] [ funnelStartDay ] [ group ] = { } ;
}
// Add remaining funnel steps/events to first step's date
for ( event in levelUserEventMap [ level ] [ user ] ) {
if ( ! levelDayGroupEventMap [ level ] [ funnelStartDay ] [ group ] [ event ] ) {
levelDayGroupEventMap [ level ] [ funnelStartDay ] [ group ] [ event ] = 0 ;
}
if ( eventFunnel [ 0 ] !== event ) levelDayGroupEventMap [ level ] [ funnelStartDay ] [ group ] [ event ] ++ ;
}
// Zero remaining funnel events
for ( var i = 1 ; i < eventFunnel . length ; i ++ ) {
var event = eventFunnel [ i ] ;
if ( ! levelDayGroupEventMap [ level ] [ funnelStartDay ] [ group ] [ event ] ) {
levelDayGroupEventMap [ level ] [ funnelStartDay ] [ group ] [ event ] = 0 ;
}
}
}
// Else no start event in this date range
}
}
// printjson(levelDayGroupEventMap);
var funnelData = [ ] ;
for ( level in levelDayGroupEventMap ) {
for ( day in levelDayGroupEventMap [ level ] ) {
for ( group in levelDayGroupEventMap [ level ] [ day ] ) {
var started = 0 ;
var finished = 0 ;
for ( event in levelDayGroupEventMap [ level ] [ day ] [ group ] ) {
if ( event === eventFunnel [ 0 ] ) {
started = levelDayGroupEventMap [ level ] [ day ] [ group ] [ event ] ;
}
else if ( event === eventFunnel [ eventFunnel . length - 1 ] ) {
finished = levelDayGroupEventMap [ level ] [ day ] [ group ] [ event ] ;
}
}
funnelData . push ( {
level : level ,
day : day ,
group : group ,
started : started ,
finished : finished
} ) ;
}
}
}
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 ;
}
return a . group < b . group ? - 1 : 1 ;
} ) ;
return funnelData ;
}