diff --git a/app/templates/editor/campaign/campaign-analytics-modal.jade b/app/templates/editor/campaign/campaign-analytics-modal.jade new file mode 100644 index 000000000..a19be9ac9 --- /dev/null +++ b/app/templates/editor/campaign/campaign-analytics-modal.jade @@ -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 \ No newline at end of file diff --git a/app/templates/editor/campaign/campaign-editor-view.jade b/app/templates/editor/campaign/campaign-editor-view.jade index 0e873ddf7..b97154825 100644 --- a/app/templates/editor/campaign/campaign-editor-view.jade +++ b/app/templates/editor/campaign/campaign-editor-view.jade @@ -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 diff --git a/app/views/editor/campaign/CampaignAnalyticsModal.coffee b/app/views/editor/campaign/CampaignAnalyticsModal.coffee new file mode 100644 index 000000000..0b47bf6b5 --- /dev/null +++ b/app/views/editor/campaign/CampaignAnalyticsModal.coffee @@ -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() diff --git a/app/views/editor/campaign/CampaignEditorView.coffee b/app/views/editor/campaign/CampaignEditorView.coffee index 7f9fe1969..db3cb140e 100644 --- a/app/views/editor/campaign/CampaignEditorView.coffee +++ b/app/views/editor/campaign/CampaignEditorView.coffee @@ -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'