Update campaign editor analytics
Add date range picker to level view.
This commit is contained in:
parent
e49c74259b
commit
3f40364616
3 changed files with 137 additions and 101 deletions
app
templates/editor/campaign
views/editor/campaign
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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', {
|
||||||
|
|
Reference in a new issue