Skip to content

Commit

Permalink
Refactor, test POST /db/analytics_perday/-/level_helps
Browse files Browse the repository at this point in the history
  • Loading branch information
sderickson committed Sep 20, 2017
1 parent 52f3f06 commit b7b6e4c
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 81 deletions.
79 changes: 0 additions & 79 deletions server/handlers/analytics_perday_handler.coffee
Expand Up @@ -14,90 +14,11 @@ class AnalyticsPerDayHandler extends Handler

getByRelationship: (req, res, args...) ->
return @sendForbiddenError res unless @hasAccess req
return @getLevelHelpsBySlugs(req, res) if args[1] is 'level_helps'
return @getLevelSubscriptionsBySlugs(req, res) if args[1] is 'level_subscriptions'
return @getRecurringRevenue(req, res) if args[1] is 'recurring_revenue'
super(arguments...)


getLevelHelpsBySlugs: (req, res) ->
# Send back an array of per-day level help buttons clicked and videos started
# Parameters:
# slugs - level slugs
# startDay - Inclusive, optional, YYYYMMDD e.g. '20141214'
# endDay - Exclusive, optional, YYYYMMDD e.g. '20141216'

levelSlugs = req.query.slugs or req.body.slugs
startDay = req.query.startDay or req.body.startDay
endDay = req.query.endDay or req.body.endDay

# log.warn "level_helps levelSlugs='#{levelSlugs}' startDay=#{startDay} endDay=#{endDay}"

return @sendSuccess res, [] unless levelSlugs?

# Cache results in app server memory for 1 day
@levelHelpsCache ?= {}
@levelHelpsCachedSince ?= new Date()
if (new Date()) - @levelHelpsCachedSince > 86400 * 1000
@levelHelpsCache = {}
@levelHelpsCachedSince = new Date()
cacheKey = levelSlugs.join ''
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
return @sendSuccess res, helps if helps = @levelHelpsCache[cacheKey]

findQueryParams = {v: {$in: ['Problem alert help clicked', 'Spell palette help clicked', 'Start help video', 'all'].concat(levelSlugs)}}
AnalyticsString.find(findQueryParams).exec (err, documents) =>
if err? then return @sendDatabaseError res, err

levelStringIDSlugMap = {}
for doc in documents
alertHelpEventID = doc._id if doc.v is 'Problem alert help clicked'
palettteHelpEventID = doc._id if doc.v is 'Spell palette help clicked'
videoHelpEventID = doc._id if doc.v is 'Start help video'
filterEventID = doc._id if doc.v is 'all'
levelStringIDSlugMap[doc._id] = doc.v if doc.v in levelSlugs

return @sendSuccess res, [] unless alertHelpEventID? and palettteHelpEventID? and videoHelpEventID? and filterEventID?

queryParams = {$and: [
{e: {$in: [alertHelpEventID, palettteHelpEventID, videoHelpEventID]}},
{f: filterEventID},
{l: {$in: Object.keys(levelStringIDSlugMap)}}
]}
queryParams["$and"].push {d: {$gte: startDay}} if startDay?
queryParams["$and"].push {d: {$lt: endDay}} if endDay?
AnalyticsPerDay.find(queryParams).exec (err, documents) =>
if err? then return @sendDatabaseError res, err

levelEventCounts = {}
for doc in documents
levelEventCounts[doc.l] ?= {}
levelEventCounts[doc.l][doc.d] ?= {}
levelEventCounts[doc.l][doc.d][doc.e] ?= 0
levelEventCounts[doc.l][doc.d][doc.e] += doc.c

helps = []
for levelID of levelEventCounts
for day of levelEventCounts[levelID]
alertHelps = 0
paletteHelps = 0
videoStarts = 0
for eventID of levelEventCounts[levelID][day]
alertHelps = levelEventCounts[levelID][day][eventID] if parseInt(eventID) is alertHelpEventID
paletteHelps = levelEventCounts[levelID][day][eventID] if parseInt(eventID) is palettteHelpEventID
videoStarts = levelEventCounts[levelID][day][eventID] if parseInt(eventID) is videoHelpEventID
helps.push
level: levelStringIDSlugMap[levelID]
day: day
alertHelps: alertHelps
paletteHelps: paletteHelps
videoStarts: videoStarts

@levelHelpsCache[cacheKey] = helps
@sendSuccess res, helps


getLevelSubscriptionsBySlugs: (req, res) ->
# Send back an array of level subscriptions shown and purchased counts
# Parameters:
Expand Down
79 changes: 78 additions & 1 deletion server/middleware/analytics-perday.coffee
Expand Up @@ -283,10 +283,87 @@ getLevelDropsBySlugs = wrap (req, res) ->
res.send(drops)


getLevelHelpsBySlugs = wrap (req, res) ->
# Send back an array of per-day level help buttons clicked and videos started
# Parameters:
# slugs - level slugs
# startDay - Inclusive, optional, YYYYMMDD e.g. '20141214'
# endDay - Exclusive, optional, YYYYMMDD e.g. '20141216'

levelSlugs = req.body.slugs
startDay = req.body.startDay
endDay = req.body.endDay

# log.warn "level_helps levelSlugs='#{levelSlugs}' startDay=#{startDay} endDay=#{endDay}"

return res.send([]) unless levelSlugs?

# Cache results in app server memory for 1 day
module.exports.levelHelpsCache ?= {}
module.exports.levelHelpsCachedSince ?= new Date()
if (new Date()) - module.exports.levelHelpsCachedSince > 86400 * 1000
module.exports.levelHelpsCache = {}
module.exports.levelHelpsCachedSince = new Date()
cacheKey = levelSlugs.join ''
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
return res.send(helps) if helps = module.exports.levelHelpsCache[cacheKey]

findQueryParams = {v: {$in: ['Problem alert help clicked', 'Spell palette help clicked', 'Start help video', 'all'].concat(levelSlugs)}}
documents = yield AnalyticsString.find(findQueryParams)

levelStringIDSlugMap = {}
for doc in documents
alertHelpEventID = doc._id if doc.v is 'Problem alert help clicked'
palettteHelpEventID = doc._id if doc.v is 'Spell palette help clicked'
videoHelpEventID = doc._id if doc.v is 'Start help video'
filterEventID = doc._id if doc.v is 'all'
levelStringIDSlugMap[doc._id] = doc.v if doc.v in levelSlugs

return res.send([]) unless alertHelpEventID? and palettteHelpEventID? and videoHelpEventID? and filterEventID?

queryParams = {$and: [
{e: {$in: [alertHelpEventID, palettteHelpEventID, videoHelpEventID]}},
{f: filterEventID},
{l: {$in: Object.keys(levelStringIDSlugMap)}}
]}
queryParams["$and"].push {d: {$gte: startDay}} if startDay?
queryParams["$and"].push {d: {$lt: endDay}} if endDay?
documents = yield AnalyticsPerDay.find(queryParams)

levelEventCounts = {}
for doc in documents
levelEventCounts[doc.l] ?= {}
levelEventCounts[doc.l][doc.d] ?= {}
levelEventCounts[doc.l][doc.d][doc.e] ?= 0
levelEventCounts[doc.l][doc.d][doc.e] += doc.c

helps = []
for levelID of levelEventCounts
for day of levelEventCounts[levelID]
alertHelps = 0
paletteHelps = 0
videoStarts = 0
for eventID of levelEventCounts[levelID][day]
alertHelps = levelEventCounts[levelID][day][eventID] if parseInt(eventID) is alertHelpEventID
paletteHelps = levelEventCounts[levelID][day][eventID] if parseInt(eventID) is palettteHelpEventID
videoStarts = levelEventCounts[levelID][day][eventID] if parseInt(eventID) is videoHelpEventID
helps.push
level: levelStringIDSlugMap[levelID]
day: day
alertHelps: alertHelps
paletteHelps: paletteHelps
videoStarts: videoStarts

module.exports.levelHelpsCache[cacheKey] = helps
res.send(helps)


module.exports = {
getActiveClasses,
getActiveUsers,
getCampaignCompletionsBySlug,
getLevelCompletionsBySlug,
getLevelDropsBySlugs
getLevelDropsBySlugs,
getLevelHelpsBySlugs
}
3 changes: 2 additions & 1 deletion server/routes/index.coffee
Expand Up @@ -69,7 +69,8 @@ module.exports.setup = (app) ->
app.post('/db/analytics_perday/-/campaign_completions', mw.auth.checkHasPermission(['admin']), mw.analyticsPerDay.getCampaignCompletionsBySlug)
app.post('/db/analytics_perday/-/level_completions', mw.auth.checkHasPermission(['admin']), mw.analyticsPerDay.getLevelCompletionsBySlug)
app.post('/db/analytics_perday/-/level_drops', mw.auth.checkHasPermission(['admin']), mw.analyticsPerDay.getLevelDropsBySlugs)

app.post('/db/analytics_perday/-/level_helps', mw.auth.checkHasPermission(['admin']), mw.analyticsPerDay.getLevelHelpsBySlugs)

Article = require '../models/Article'
app.get('/db/article', mw.rest.get(Article))
app.post('/db/article', mw.auth.checkLoggedIn(), mw.auth.checkHasPermission(['admin', 'artisan']), mw.rest.post(Article))
Expand Down
62 changes: 62 additions & 0 deletions spec/server/functional/analytics-perday.spec.coffee
Expand Up @@ -233,6 +233,68 @@ describe 'POST /db/analytics_perday/-/level_drops', ->
expect(res.statusCode).toBe(200)
expect(AnalyticsPerDay.find.calls.count()).toBe(1)

it 'accepts start and date inputs', utils.wrap ->
json = {
slugs: [@level.get('slug')]
startDay: '20140101'
endDay: '20160101'
}
[res] = yield request.postAsync({@url, json})
expect(res.body.length).toBe(1)

json = {
slugs: [@level.get('slug')]
startDay: '20160101'
endDay: '20180101'
}
[res] = yield request.postAsync({@url, json})
expect(res.body.length).toBe(0)


describe 'POST /db/analytics_perday/-/level_helps', ->
beforeEach utils.wrap ->
admin = yield utils.initAdmin()
yield utils.loginUser(admin)

@level = yield utils.makeLevel()
levelString = yield utils.makeAnalyticsString({v:@level.get('slug')})
alertString = yield utils.makeAnalyticsString({v: 'Problem alert help clicked'})
paletteString = yield utils.makeAnalyticsString({v: 'Spell palette help clicked'})
videoString = yield utils.makeAnalyticsString({v: 'Start help video'})

allString = yield utils.makeAnalyticsString({v:'all'})
i = 100
yield utils.makeAnalyticsPerDay({d: '20150101', c: i++}, {e: alertString, f: allString, l:levelString})
yield utils.makeAnalyticsPerDay({d: '20150101', c: i++}, {e: paletteString, f: allString, l:levelString})
yield utils.makeAnalyticsPerDay({d: '20150101', c: i++}, {e: videoString, f: allString, l:levelString})

@url = utils.getUrl('/db/analytics_perday/-/level_helps')
@json = { slugs: [@level.get('slug')] }


it 'returns 403 unless you are an admin', utils.wrap ->
user = yield utils.initUser()
yield utils.loginUser(user)
[res] = yield request.postAsync({@url, @json})
expect(res.statusCode).toBe(403)

it 'returns start and finish data for levels in a given level, and saves a cache', utils.wrap ->
spyOn(AnalyticsPerDay, 'find').and.callThrough()
expect(middleware.analyticsPerDay.levelHelpsCache).toBeUndefined()
[res] = yield request.postAsync({@url, @json})
expect(res.body).toEqual([ {
level: @level.get('slug')
day: '20150101',
alertHelps: 100,
paletteHelps: 101,
videoStarts: 102
} ])
expect(middleware.analyticsPerDay.levelHelpsCache).toBeDefined()
expect(AnalyticsPerDay.find.calls.count()).toBe(1)
[res] = yield request.postAsync({@url, @json})
expect(res.statusCode).toBe(200)
expect(AnalyticsPerDay.find.calls.count()).toBe(1)

it 'accepts start and date inputs', utils.wrap ->
json = {
slugs: [@level.get('slug')]
Expand Down

0 comments on commit b7b6e4c

Please sign in to comment.