mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-26 05:53:39 -04:00
Add date picker to campaign editor analytics
Only for overall campaign editor analytics. Still need to add date pickers to individual level views. Moving campaign editor analytics to its own modal to support reloading with new dates. The date picker widget works poorly, but you can type in dates if necessary. Also moving analytics button up into the top right header, with a little stats glyph icon.
This commit is contained in:
parent
974c9e538d
commit
d532d9fe6e
4 changed files with 150 additions and 101 deletions
app
templates/editor/campaign
views/editor/campaign
37
app/templates/editor/campaign/campaign-analytics-modal.jade
Normal file
37
app/templates/editor/campaign/campaign-analytics-modal.jade
Normal file
|
@ -0,0 +1,37 @@
|
|||
extends /templates/core/modal-base
|
||||
|
||||
block modal-header-content
|
||||
h3 Campaign Analytics
|
||||
if campaignCompletions.startDay && campaignCompletions.endDay
|
||||
.input-group.input-group-sm
|
||||
input.form-control#input-startday(type='text', style='width:100px;', value=campaignCompletions.startDay)
|
||||
input.form-control#input-endday(type='text', style='width:100px;', value=campaignCompletions.endDay)
|
||||
button.btn.btn-default.btn-sm#reload-button(style='margin-left:10px;') Reload
|
||||
|
||||
block modal-body-content
|
||||
if campaignCompletions && campaignCompletions.levels
|
||||
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
|
||||
thead
|
||||
tr
|
||||
td Level
|
||||
td Started
|
||||
td Finished
|
||||
td Playtime (s)
|
||||
td Completion %
|
||||
tbody
|
||||
- for (var i = 0; i < campaignCompletions.levels.length; i++)
|
||||
tr
|
||||
td= campaignCompletions.levels[i].level
|
||||
td= campaignCompletions.levels[i].started
|
||||
td= campaignCompletions.levels[i].finished
|
||||
td= campaignCompletions.levels[i].averagePlaytime
|
||||
if campaignCompletions.top3.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td(style='background-color:lightblue;')= campaignCompletions.levels[i].completionRate
|
||||
else if campaignCompletions.bottom3.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td(style='background-color:pink;')= campaignCompletions.levels[i].completionRate
|
||||
else
|
||||
td= campaignCompletions.levels[i].completionRate
|
||||
else
|
||||
div Loading...
|
||||
|
||||
block modal-footer
|
|
@ -16,6 +16,9 @@ block header
|
|||
span.glyphicon-home.glyphicon
|
||||
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
li#analytics-button
|
||||
a
|
||||
span.glyphicon-stats.glyphicon
|
||||
if me.isAdmin()
|
||||
li#save-button
|
||||
a
|
||||
|
@ -40,43 +43,5 @@ block outer_content
|
|||
#right-column
|
||||
#campaign-view
|
||||
#campaign-level-view.hidden
|
||||
if campaignCompletions
|
||||
button.btn.btn-default#analytics-button(title="Analytics", data-toggle="modal" data-target="#analytics-modal") Analytics
|
||||
.modal.fade#analytics-modal(tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true")
|
||||
.modal-dialog.modal-lg
|
||||
.modal-content
|
||||
.modal-header
|
||||
button.close(type="button", data-dismiss="modal", aria-label="Close")
|
||||
span(aria-hidden="true") ×
|
||||
h4.modal-title Analytics
|
||||
.modal-body
|
||||
if campaignCompletions.startDay
|
||||
if campaignCompletions.endDay
|
||||
div #{campaignCompletions.startDay} to #{campaignCompletions.endDay}
|
||||
else
|
||||
div #{campaignCompletions.startDay} to yesterday
|
||||
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
|
||||
thead
|
||||
tr
|
||||
td Level
|
||||
td Started
|
||||
td Finished
|
||||
td Playtime (s)
|
||||
td Completion %
|
||||
tbody
|
||||
- for (var i = 0; i < campaignCompletions.levels.length; i++)
|
||||
tr
|
||||
td= campaignCompletions.levels[i].level
|
||||
td= campaignCompletions.levels[i].started
|
||||
td= campaignCompletions.levels[i].finished
|
||||
td= campaignCompletions.levels[i].averagePlaytime
|
||||
if campaignCompletions.top3.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td(style='background-color:lightblue;')= campaignCompletions.levels[i].completionRate
|
||||
else if campaignCompletions.bottom3.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td(style='background-color:pink;')= campaignCompletions.levels[i].completionRate
|
||||
else
|
||||
td= campaignCompletions.levels[i].completionRate
|
||||
else
|
||||
button.btn.btn-default.disabled#analytics-button Analytics Loading...
|
||||
|
||||
block footer
|
||||
|
|
101
app/views/editor/campaign/CampaignAnalyticsModal.coffee
Normal file
101
app/views/editor/campaign/CampaignAnalyticsModal.coffee
Normal file
|
@ -0,0 +1,101 @@
|
|||
template = require 'templates/editor/campaign/campaign-analytics-modal'
|
||||
utils = require 'core/utils'
|
||||
ModalView = require 'views/core/ModalView'
|
||||
|
||||
# TODO: jquery-ui datepicker doesn't work well in this view
|
||||
# TODO: the date format handling is confusing (yyyy-mm-dd <=> yyyymmdd)
|
||||
|
||||
module.exports = class CampaignAnalyticsModal extends ModalView
|
||||
id: 'campaign-analytics-modal'
|
||||
template: template
|
||||
plain: true
|
||||
|
||||
events:
|
||||
'click #reload-button': 'onClickReloadButton'
|
||||
|
||||
constructor: (options, @campaignHandle, @campaignCompletions) ->
|
||||
super options
|
||||
@getCampaignAnalytics() unless @campaignCompletions?.levels?
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.campaignCompletions = @campaignCompletions
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
$("#input-startday").datepicker dateFormat: "yy-mm-dd"
|
||||
$("#input-endday").datepicker dateFormat: "yy-mm-dd"
|
||||
|
||||
onClickReloadButton: () =>
|
||||
startDay = $('#input-startday').val()
|
||||
endDay = $('#input-endday').val()
|
||||
delete @campaignCompletions.levels
|
||||
@render()
|
||||
@getCampaignAnalytics startDay, endDay
|
||||
|
||||
getCampaignAnalytics: (startDay, endDay) =>
|
||||
# Fetch campaign analytics, unless dates given
|
||||
|
||||
startDay = startDay.replace(/-/g, '') if startDay?
|
||||
endDay = endDay.replace(/-/g, '') if endDay?
|
||||
|
||||
startDay ?= utils.getUTCDay -14
|
||||
endDay ?= utils.getUTCDay -1
|
||||
|
||||
success = (data) =>
|
||||
return if @destroyed
|
||||
mapFn = (item) ->
|
||||
item.completionRate = (item.finished / item.started * 100).toFixed(2)
|
||||
item
|
||||
@campaignCompletions.levels = _.map data, mapFn, @
|
||||
sortedLevels = _.cloneDeep @campaignCompletions.levels
|
||||
sortedLevels = _.filter sortedLevels, ((a) -> a.finished >= 10), @
|
||||
sortedLevels.sort (a, b) -> b.completionRate - a.completionRate
|
||||
@campaignCompletions.top3 = _.pluck sortedLevels[0..2], 'level'
|
||||
sortedLevels.sort (a, b) -> a.completionRate - b.completionRate
|
||||
@campaignCompletions.bottom3 = _.pluck sortedLevels[0..2], 'level'
|
||||
@campaignCompletions.startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
|
||||
@campaignCompletions.endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
|
||||
@getCampaignAveragePlaytimes startDay, endDay
|
||||
|
||||
# TODO: Why do we need this url dash?
|
||||
request = @supermodel.addRequestResource 'campaign_completions', {
|
||||
url: '/db/analytics_perday/-/campaign_completions'
|
||||
data: {startDay: startDay, endDay: endDay, slug: @campaignHandle}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
request.load()
|
||||
|
||||
getCampaignAveragePlaytimes: (startDay, endDay) =>
|
||||
# Fetch level average playtimes
|
||||
success = (data) =>
|
||||
return if @destroyed
|
||||
levelAverages = {}
|
||||
for item in data
|
||||
levelAverages[item.level] ?= []
|
||||
levelAverages[item.level].push item.average
|
||||
for level in @campaignCompletions.levels
|
||||
if levelAverages[level.level]
|
||||
if levelAverages[level.level].length > 0
|
||||
total = _.reduce levelAverages[level.level], ((sum, num) -> sum + num)
|
||||
level.averagePlaytime = (total / levelAverages[level.level].length).toFixed(2)
|
||||
else
|
||||
level.averagePlaytime = 0.0
|
||||
@render()
|
||||
|
||||
startDay ?= utils.getUTCDay -14
|
||||
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
|
||||
endDay ?= utils.getUTCDay -1
|
||||
endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
|
||||
|
||||
levelSlugs = _.pluck @campaignCompletions.levels, 'level'
|
||||
|
||||
request = @supermodel.addRequestResource 'playtime_averages', {
|
||||
url: '/db/level/-/playtime_averages'
|
||||
data: {startDay: startDay, endDay: endDay, slugs: levelSlugs}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
request.load()
|
|
@ -7,9 +7,10 @@ CampaignView = require 'views/play/CampaignView'
|
|||
CocoCollection = require 'collections/CocoCollection'
|
||||
treemaExt = require 'core/treema-ext'
|
||||
utils = require 'core/utils'
|
||||
SaveCampaignModal = require './SaveCampaignModal'
|
||||
RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection'
|
||||
CampaignAnalyticsModal = require './CampaignAnalyticsModal'
|
||||
CampaignLevelView = require './CampaignLevelView'
|
||||
SaveCampaignModal = require './SaveCampaignModal'
|
||||
|
||||
achievementProject = ['related', 'rewards', 'name', 'slug']
|
||||
thangTypeProject = ['name', 'original']
|
||||
|
@ -20,6 +21,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
className: 'editor'
|
||||
|
||||
events:
|
||||
'click #analytics-button': 'onClickAnalyticsButton'
|
||||
'click #save-button': 'onClickSaveButton'
|
||||
|
||||
constructor: (options, @campaignHandle) ->
|
||||
|
@ -27,6 +29,9 @@ module.exports = class CampaignEditorView extends RootView
|
|||
@campaign = new Campaign({_id:@campaignHandle})
|
||||
@supermodel.loadModel(@campaign, 'campaign')
|
||||
|
||||
# Save reference to data used by anlytics modal so it persists across modal open/closes.
|
||||
@campaignAnalytics = {}
|
||||
|
||||
@levels = new CocoCollection([], {
|
||||
model: Level
|
||||
url: "/db/campaign/#{@campaignHandle}/levels"
|
||||
|
@ -47,8 +52,6 @@ module.exports = class CampaignEditorView extends RootView
|
|||
@listenToOnce @levels, 'sync', @onFundamentalLoaded
|
||||
@listenToOnce @achievements, 'sync', @onFundamentalLoaded
|
||||
|
||||
_.delay @getCampaignAnalytics, 500
|
||||
|
||||
loadThangTypeNames: ->
|
||||
# Load the names of the ThangTypes that this level's Treema nodes might want to display.
|
||||
originals = []
|
||||
|
@ -132,9 +135,11 @@ module.exports = class CampaignEditorView extends RootView
|
|||
getRenderData: ->
|
||||
c = super()
|
||||
c.campaign = @campaign
|
||||
c.campaignCompletions = @campaignCompletions
|
||||
c
|
||||
|
||||
onClickAnalyticsButton: ->
|
||||
@openModalView new CampaignAnalyticsModal {}, @campaignHandle, @campaignAnalytics
|
||||
|
||||
onClickSaveButton: ->
|
||||
@toSave.set @toSave.filter (m) -> m.hasLocalChanges()
|
||||
@openModalView new SaveCampaignModal({}, @toSave)
|
||||
|
@ -240,65 +245,6 @@ module.exports = class CampaignEditorView extends RootView
|
|||
if achievement.hasLocalChanges()
|
||||
@toSave.add achievement
|
||||
|
||||
getCampaignAnalytics: =>
|
||||
# Fetch last 14 days of campaign analytics
|
||||
|
||||
startDay = utils.getUTCDay -14
|
||||
endDay = utils.getUTCDay -1
|
||||
|
||||
success = (data) =>
|
||||
return if @destroyed
|
||||
mapFn = (item) ->
|
||||
item.completionRate = (item.finished / item.started * 100).toFixed(2)
|
||||
item
|
||||
@campaignCompletions = levels: _.map data, mapFn, @
|
||||
sortedLevels = _.cloneDeep @campaignCompletions.levels
|
||||
sortedLevels = _.filter sortedLevels, ((a) -> a.completionRate > 1.0), @
|
||||
sortedLevels.sort (a, b) -> b.completionRate - a.completionRate
|
||||
@campaignCompletions.top3 = _.pluck sortedLevels[0..2], 'level'
|
||||
sortedLevels.sort (a, b) -> a.completionRate - b.completionRate
|
||||
@campaignCompletions.bottom3 = _.pluck sortedLevels[0..2], 'level'
|
||||
@campaignCompletions.startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
|
||||
@campaignCompletions.endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
|
||||
@getCampaignAveragePlaytimes()
|
||||
|
||||
# TODO: Why do we need this url dash?
|
||||
request = @supermodel.addRequestResource 'campaign_completions', {
|
||||
url: '/db/analytics_perday/-/campaign_completions'
|
||||
data: {startDay: startDay, slug: @campaignHandle}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
request.load()
|
||||
|
||||
getCampaignAveragePlaytimes: =>
|
||||
# Fetch last 14 days of level average playtimes
|
||||
success = (data) =>
|
||||
return if @destroyed
|
||||
levelAverages = {}
|
||||
for item in data
|
||||
levelAverages[item.level] ?= []
|
||||
levelAverages[item.level].push item.average
|
||||
for level in @campaignCompletions.levels
|
||||
if levelAverages[level.level]
|
||||
if levelAverages[level.level].length > 0
|
||||
total = _.reduce levelAverages[level.level], ((sum, num) -> sum + num)
|
||||
level.averagePlaytime = (total / levelAverages[level.level].length).toFixed(2)
|
||||
else
|
||||
level.averagePlaytime = 0.0
|
||||
@render()
|
||||
|
||||
levelSlugs = _.pluck @campaignCompletions.levels, 'level'
|
||||
startDay = utils.getUTCDay -14
|
||||
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
|
||||
request = @supermodel.addRequestResource 'playtime_averages', {
|
||||
url: '/db/level/-/playtime_averages'
|
||||
data: {startDay: startDay, slugs: levelSlugs}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
request.load()
|
||||
|
||||
|
||||
class LevelsNode extends TreemaObjectNode
|
||||
valueClass: 'treema-levels'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue