From d532d9fe6e536d65255a35e87e073668c924e6f9 Mon Sep 17 00:00:00 2001
From: Matt Lott <mattlott@live.com>
Date: Fri, 16 Jan 2015 15:44:24 -0800
Subject: [PATCH] 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.
---
 .../campaign/campaign-analytics-modal.jade    |  37 +++++++
 .../editor/campaign/campaign-editor-view.jade |  41 +------
 .../campaign/CampaignAnalyticsModal.coffee    | 101 ++++++++++++++++++
 .../editor/campaign/CampaignEditorView.coffee |  72 ++-----------
 4 files changed, 150 insertions(+), 101 deletions(-)
 create mode 100644 app/templates/editor/campaign/campaign-analytics-modal.jade
 create mode 100644 app/views/editor/campaign/CampaignAnalyticsModal.coffee

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") &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
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'