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:
Matt Lott 2015-01-16 15:44:24 -08:00
parent 974c9e538d
commit d532d9fe6e
4 changed files with 150 additions and 101 deletions

View 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

View file

@ -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") &times;
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

View 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()

View file

@ -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'