Update campaign editor analytics

Add date range picker to level view.
This commit is contained in:
Matt Lott 2015-01-21 16:03:50 -08:00
parent e49c74259b
commit 3f40364616
3 changed files with 137 additions and 101 deletions

View file

@ -5,8 +5,15 @@
a(href="/editor/level/#{level.get('slug')}", target="_blank") (edit) a(href="/editor/level/#{level.get('slug')}", target="_blank") (edit)
p= level.get('description') p= level.get('description')
if analytics.startDay && analytics.endDay
.input-group.input-group-sm
input.form-control#input-startday(type='text', style='width:100px;', value=analytics.startDay)
input.form-control#input-endday(type='text', style='width:100px;', value=analytics.endDay)
button.btn.btn-default.btn-sm#reload-button(style='margin-left:10px;') Reload
h4 Completion Rates h4 Completion Rates
if levelCompletions if analytics.levelCompletions.loading
div Loading...
else
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt') table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead thead
tr tr
@ -14,48 +21,43 @@
td Started td Started
td Finished td Finished
td Completion % td Completion %
if levelHelps && levelHelps.length === levelCompletions.length if analytics.levelHelps.levels.length === analytics.levelCompletions.levels.length
td Helps Clicked td Helps Clicked
td Helps / Started td Helps / Started
td Help Videos td Help Videos
td Videos / Started td Videos / Started
tbody tbody
- for (var i = 0; i < levelCompletions.length; i++) - for (var i = 0; i < analytics.levelCompletions.levels.length; i++)
tr tr
td= levelCompletions[i].created td= analytics.levelCompletions.levels[i].created
td= levelCompletions[i].started td= analytics.levelCompletions.levels[i].started
td= levelCompletions[i].finished td= analytics.levelCompletions.levels[i].finished
td= levelCompletions[i].rate td= analytics.levelCompletions.levels[i].rate
if levelHelps && levelHelps.length === levelCompletions.length if analytics.levelHelps.levels.length === analytics.levelCompletions.levels.length && analytics.levelCompletions.levels[i].created == analytics.levelHelps.levels[i].day
td= levelHelps[i].alertHelps + levelHelps[i].paletteHelps td= analytics.levelHelps.levels[i].alertHelps + analytics.levelHelps.levels[i].paletteHelps
td= ((levelHelps[i].alertHelps + levelHelps[i].paletteHelps) / levelCompletions[i].started).toFixed(2) td= ((analytics.levelHelps.levels[i].alertHelps + analytics.levelHelps.levels[i].paletteHelps) / analytics.levelCompletions.levels[i].started).toFixed(2)
td= levelHelps[i].videoStarts td= analytics.levelHelps.levels[i].videoStarts
td= (levelHelps[i].videoStarts / levelCompletions[i].started).toFixed(2) td= (analytics.levelHelps.levels[i].videoStarts / analytics.levelCompletions.levels[i].started).toFixed(2)
else
div Loading...
h4 Average Playtimes h4 Average Playtimes
if levelPlaytimes if analytics.levelPlaytimes.loading
div Loading...
else
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt') table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead thead
tr tr
td Date td Date
td Average (s) td Average (s)
tbody tbody
- for (var i = 0; i < levelPlaytimes.length; i++) - for (var i = 0; i < analytics.levelPlaytimes.levels.length; i++)
tr tr
td= levelPlaytimes[i].created td= analytics.levelPlaytimes.levels[i].created
td= levelPlaytimes[i].average.toFixed(2) td= analytics.levelPlaytimes.levels[i].average.toFixed(2)
else
div Loading...
h4 Common Problems h4 Common Problems
if commonProblems if analytics.commonProblems.loading
if commonProblems.startDay div Loading...
if commonProblems.endDay else
div(style='font-size:10pt') #{commonProblems.startDay} to #{commonProblems.endDay}
else
div(style='font-size:10pt') #{commonProblems.startDay} to today
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt') table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead thead
tr tr
@ -64,18 +66,18 @@
td Error Hint td Error Hint
td Count td Count
tbody tbody
- for (var i = 0; i < commonProblems.length && i < 20; i++) - for (var i = 0; i < analytics.commonProblems.levels.length && i < 20; i++)
tr tr
td= commonProblems[i].language td= analytics.commonProblems.levels[i].language
td= commonProblems[i].message td= analytics.commonProblems.levels[i].message
td= commonProblems[i].hint td= analytics.commonProblems.levels[i].hint
td= commonProblems[i].count td= analytics.commonProblems.levels[i].count
else
div Loading...
h4 Recent Sessions h4 Recent Sessions
if recentSessions if analytics.recentSessions.loading
div(style='font-size:10pt') Latest #{recentSessions.length} sessions for this level div Loading...
else
div(style='font-size:10pt') Latest #{analytics.recentSessions.levels.length} sessions for this level
div(style='font-size:10pt') Double-click row to open player and session div(style='font-size:10pt') Double-click row to open player and session
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt') table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead thead
@ -87,19 +89,17 @@
td Complete td Complete
td Changed td Changed
tbody tbody
- for (var i = 0; i < recentSessions.length; i++) - for (var i = 0; i < analytics.recentSessions.levels.length; i++)
tr.recent-session(data-player-id=recentSessions[i].creator, data-session-id=recentSessions[i]._id) tr.recent-session(data-player-id=analytics.recentSessions.levels[i].creator, data-session-id=analytics.recentSessions.levels[i]._id)
td= recentSessions[i]._id td= analytics.recentSessions.levels[i]._id
td= recentSessions[i].creatorName || recentSessions[i].creator td= analytics.recentSessions.levels[i].creatorName || analytics.recentSessions.levels[i].creator
td= recentSessions[i].codeLanguage td= analytics.recentSessions.levels[i].codeLanguage
td= recentSessions[i].playtime td= analytics.recentSessions.levels[i].playtime
if recentSessions[i].state && recentSessions[i].state.complete if analytics.recentSessions.levels[i].state && analytics.recentSessions.levels[i].state.complete
td= recentSessions[i].state.complete td= analytics.recentSessions.levels[i].state.complete
else else
td false td false
td= recentSessions[i].changed td= analytics.recentSessions.levels[i].changed
else
div Loading...
if level.get('tasks') if level.get('tasks')
.tasks .tasks

View file

@ -25,9 +25,9 @@ module.exports = class CampaignAnalyticsModal extends ModalView
getRenderData: -> getRenderData: ->
c = super() c = super()
c.campaignCompletions = @campaignCompletions
c.showLeftGame = @showLeftGame c.showLeftGame = @showLeftGame
c.showSubscriptions = @showSubscriptions c.showSubscriptions = @showSubscriptions
c.campaignCompletions = @campaignCompletions
c c
afterRender: -> afterRender: ->
@ -124,13 +124,13 @@ module.exports = class CampaignAnalyticsModal extends ModalView
# Chain these together so we can calculate relative metrics (e.g. left game per second) # Chain these together so we can calculate relative metrics (e.g. left game per second)
@getCampaignLevelCompletions startDay, endDay, () => @getCampaignLevelCompletions startDay, endDay, () =>
@render() @render?()
@getCompaignLevelDrops startDay, endDay, () => @getCompaignLevelDrops startDay, endDay, () =>
@render() @render?()
@getCampaignAveragePlaytimes startDayDashed, endDayDashed, () => @getCampaignAveragePlaytimes startDayDashed, endDayDashed, () =>
@render() @render?()
@getCampaignLevelSubscriptions startDay, endDay, () => @getCampaignLevelSubscriptions startDay, endDay, () =>
@render() @render?()
getCampaignAveragePlaytimes: (startDay, endDay, doneCallback) => getCampaignAveragePlaytimes: (startDay, endDay, doneCallback) =>
# Fetch level average playtimes # Fetch level average playtimes

View file

@ -11,6 +11,7 @@ module.exports = class CampaignLevelView extends CocoView
events: events:
'click .close': 'onClickClose' 'click .close': 'onClickClose'
'click #reload-button': 'onClickReloadButton'
'dblclick .recent-session': 'onDblClickRecentSession' 'dblclick .recent-session': 'onDblClickRecentSession'
constructor: (options, @level) -> constructor: (options, @level) ->
@ -20,26 +21,28 @@ module.exports = class CampaignLevelView extends CocoView
@listenToOnce @fullLevel, 'sync', => @render?() @listenToOnce @fullLevel, 'sync', => @render?()
@levelSlug = @level.get('slug') @levelSlug = @level.get('slug')
@getCommonLevelProblems() @getAnalytics()
@getLevelCompletions()
@getLevelHelps()
@getLevelPlaytimes()
@getRecentSessions()
getRenderData: -> getRenderData: ->
c = super() c = super()
c.level = if @fullLevel.loaded then @fullLevel else @level c.level = if @fullLevel.loaded then @fullLevel else @level
c.commonProblems = @commonProblems c.analytics = @analytics
c.levelCompletions = @levelCompletions
c.levelHelps = @levelHelps
c.levelPlaytimes = @levelPlaytimes
c.recentSessions = @recentSessions
c c
afterRender: ->
super()
$("#input-startday").datepicker dateFormat: "yy-mm-dd"
$("#input-endday").datepicker dateFormat: "yy-mm-dd"
onClickClose: -> onClickClose: ->
@$el.addClass('hidden') @$el.addClass('hidden')
@trigger 'hidden' @trigger 'hidden'
onClickReloadButton: () =>
startDay = $('#input-startday').val()
endDay = $('#input-endday').val()
@getAnalytics startDay, endDay
onDblClickRecentSession: (e) -> onDblClickRecentSession: (e) ->
# Admin view of players' code # Admin view of players' code
return unless me.isAdmin() return unless me.isAdmin()
@ -48,91 +51,124 @@ module.exports = class CampaignLevelView extends CocoView
session = new LevelSession _id: row.data 'session-id' session = new LevelSession _id: row.data 'session-id'
@openModalView new ModelModal models: [session, player] @openModalView new ModelModal models: [session, player]
getCommonLevelProblems: -> getAnalytics: (startDay, endDay) =>
# Fetch last 30 days of common level problems if startDay?
startDay = utils.getUTCDay -29 startDayDashed = startDay
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}" startDay = startDay.replace(/-/g, '')
else
startDay = utils.getUTCDay -14
startDayDashed = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
if endDay?
endDayDashed = endDay
endDay = endDay.replace(/-/g, '')
else
endDay = utils.getUTCDay -1
endDayDashed = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
success = (data) => @analytics =
return if @destroyed startDay: startDayDashed
@commonProblems = data endDay: endDayDashed
@commonProblems.startDay = startDay commonProblems:
levels: []
loading: true
levelCompletions:
levels: []
loading: true
levelHelps:
levels: []
loading: true
levelPlaytimes:
levels: []
loading: true
recentSessions:
levels: []
loading: true
@render()
@getCommonLevelProblems startDayDashed, endDayDashed, () =>
@analytics.commonProblems.loading = false
@render() @render()
@getLevelCompletions startDay, endDay, () =>
@analytics.levelCompletions.loading = false
@render()
@getLevelHelps startDay, endDay, () =>
@analytics.levelHelps.loading = false
@render()
@getLevelPlaytimes startDayDashed, endDayDashed, () =>
@analytics.levelPlaytimes.loading = false
@render()
@getRecentSessions () =>
@analytics.recentSessions.loading = false
@render()
getCommonLevelProblems: (startDay, endDay, doneCallback) ->
success = (data) =>
return doneCallback() if @destroyed
@analytics.commonProblems.levels = data
doneCallback()
# TODO: Why do we need this url dash? # TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'common_problems', { request = @supermodel.addRequestResource 'common_problems', {
url: '/db/user_code_problem/-/common_problems' url: '/db/user_code_problem/-/common_problems'
data: {startDay: startDay, slug: @levelSlug} data: {startDay: startDay, endDay: endDay, slug: @levelSlug}
method: 'POST' method: 'POST'
success: success success: success
}, 0 }, 0
request.load() request.load()
getLevelCompletions: -> getLevelCompletions: (startDay, endDay, doneCallback) ->
# Fetch last 14 days of level completion counts
success = (data) => success = (data) =>
return if @destroyed return doneCallback() if @destroyed
data.sort (a, b) -> if a.created < b.created then 1 else -1 data.sort (a, b) -> if a.created < b.created then 1 else -1
mapFn = (item) -> mapFn = (item) ->
item.rate = (item.finished / item.started * 100).toFixed(2) item.rate = (item.finished / item.started * 100).toFixed(2)
item item
@levelCompletions = _.map data, mapFn, @ @analytics.levelCompletions.levels = _.map data, mapFn, @
@render() doneCallback()
startDay = utils.getUTCDay -14
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'level_completions', { request = @supermodel.addRequestResource 'level_completions', {
url: '/db/analytics_perday/-/level_completions' url: '/db/analytics_perday/-/level_completions'
data: {startDay: startDay, slug: @levelSlug} data: {startDay: startDay, endDay: endDay, slug: @levelSlug}
method: 'POST' method: 'POST'
success: success success: success
}, 0 }, 0
request.load() request.load()
getLevelHelps: -> getLevelHelps: (startDay, endDay, doneCallback) ->
# Fetch last 14 days of level completion counts
success = (data) => success = (data) =>
return if @destroyed return doneCallback() if @destroyed
@levelHelps = data.sort (a, b) -> if a.created < b.created then 1 else -1 @analytics.levelHelps.levels = data.sort (a, b) -> if a.day < b.day then 1 else -1
@render() doneCallback()
startDay = utils.getUTCDay -14
request = @supermodel.addRequestResource 'level_helps', { request = @supermodel.addRequestResource 'level_helps', {
url: '/db/analytics_perday/-/level_helps' url: '/db/analytics_perday/-/level_helps'
data: {startDay: startDay, slugs: [@levelSlug]} data: {startDay: startDay, endDay: endDay, slugs: [@levelSlug]}
method: 'POST' method: 'POST'
success: success success: success
}, 0 }, 0
request.load() request.load()
getLevelPlaytimes: -> getLevelPlaytimes: (startDay, endDay, doneCallback) ->
# Fetch last 14 days of level average playtimes
success = (data) => success = (data) =>
return if @destroyed return doneCallback() if @destroyed
@levelPlaytimes = data.sort (a, b) -> if a.created < b.created then 1 else -1 @analytics.levelPlaytimes.levels = data.sort (a, b) -> if a.created < b.created then 1 else -1
@render() doneCallback()
startDay = utils.getUTCDay -13
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'playtime_averages', { request = @supermodel.addRequestResource 'playtime_averages', {
url: '/db/level/-/playtime_averages' url: '/db/level/-/playtime_averages'
data: {startDay: startDay, slugs: [@levelSlug]} data: {startDay: startDay, endDay: endDay, slugs: [@levelSlug]}
method: 'POST' method: 'POST'
success: success success: success
}, 0 }, 0
request.load() request.load()
getRecentSessions: -> getRecentSessions: (doneCallback) ->
limit = 100 limit = 100
success = (data) => success = (data) =>
return if @destroyed return doneCallback() if @destroyed
@recentSessions = data @analytics.recentSessions.levels = data
@render() doneCallback()
# TODO: Why do we need this url dash? # TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'level_sessions_recent', { request = @supermodel.addRequestResource 'level_sessions_recent', {