From 019406a341a516cd556bb23c337bbafbae3d1e5c Mon Sep 17 00:00:00 2001
From: Matt Lott <mattlott@live.com>
Date: Mon, 26 Jan 2015 14:58:35 -0800
Subject: [PATCH] Update campaign analytics

Increase line graph dots for larger hover targets.
Update missing day data handling to fill in graph points for any
missing day, not just most recent end days.
Fix level completion div0 bug.
---
 .../editor/campaign/CampaignLevelView.coffee  | 100 +++++++++---------
 server/levels/level_handler.coffee            |   2 +
 2 files changed, 51 insertions(+), 51 deletions(-)

diff --git a/app/views/editor/campaign/CampaignLevelView.coffee b/app/views/editor/campaign/CampaignLevelView.coffee
index 235a7a31e..96358c032 100644
--- a/app/views/editor/campaign/CampaignLevelView.coffee
+++ b/app/views/editor/campaign/CampaignLevelView.coffee
@@ -103,12 +103,22 @@ module.exports = class CampaignLevelView extends CocoView
       description: 'Help video rate (%)'
       color: 'purple'
 
-    # Last day may be missing due to caching, will use this days aggregate to clean up individual graph lines
+    # Use this days aggregate to fill in missing days from the analytics data
     days = {}
-    days[day.created] = true for day in @analytics.levelCompletions.data if @analytics?.levelCompletions?.data?
-    days[day.created.replace(/-/g, '')] = true for day in @analytics.levelPlaytimes.data if @analytics?.levelPlaytimes?.data?
-    days[day.day] = true for day in @analytics.levelHelps.data if @analytics?.levelHelps?.data?
+    days["#{day.created[0..3]}-#{day.created[4..5]}-#{day.created[6..7]}"] = true for day in @analytics.levelCompletions.data if @analytics?.levelCompletions?.data?
+    days[day.created] = true for day in @analytics.levelPlaytimes.data if @analytics?.levelPlaytimes?.data?
+    days["#{day.day[0..3]}-#{day.day[4..5]}-#{day.day[6..7]}"] = true for day in @analytics.levelHelps.data if @analytics?.levelHelps?.data?
     days = Object.keys(days).sort (a, b) -> if a < b then -1 else 1
+    if days.length > 0
+      currentIndex = 0
+      currentDay = days[currentIndex]
+      currentDate = new Date(currentDay + "T00:00:00.000Z")
+      lastDay = days[days.length - 1]
+      while currentDay isnt lastDay
+        days.splice currentIndex, 0, currentDay if days[currentIndex] isnt currentDay
+        currentIndex++
+        currentDate.setUTCDate(currentDate.getUTCDate() + 1)
+        currentDay = currentDate.toISOString().substr(0, 10)
 
     # Update level completion graph data
     dayStartedMap = {}
@@ -126,17 +136,14 @@ module.exports = class CampaignLevelView extends CocoView
           pointID: "#{completionLineID}#{i}"
           values: ["Started: #{day.started}", "Finished: #{day.finished}", "Completion rate: #{rate.toFixed(2)}%"]
       # Ensure points for each day
-      if levelPoints.length < days.length
-        for i in [1..days.length - levelPoints.length]
-          day = days[days.length - i]
-          x = levelPoints[levelPoints.length - 1].x + 1
-          levelPoints.push
-            x: x
+      for day, i in days
+        if levelPoints.length <= i or levelPoints[i].day isnt day
+          levelPoints.splice i, 0,
             y: 0.0
-            started: 0
-            day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
-            pointID: "#{completionLineID}#{x}"
+            day: day
             values: []
+        levelPoints[i].x = i
+        levelPoints[i].pointID = "#{completionLineID}#{i}"
       @analytics.graphs[0].lines.push
         lineID: completionLineID
         enabled: true
@@ -159,17 +166,14 @@ module.exports = class CampaignLevelView extends CocoView
           pointID: "#{playtimeLineID}#{i}"
           values: ["Average playtime: #{avg.toFixed(2)}s"]
       # Ensure points for each day
-      if playtimePoints.length < days.length
-        for i in [1..days.length - playtimePoints.length]
-          day = days[days.length - i]
-          x = playtimePoints[playtimePoints.length - 1].x + 1
-          playtimePoints.push
-            x: x
+      for day, i in days
+        if playtimePoints.length <= i or playtimePoints[i].day isnt day
+          playtimePoints.splice i, 0,
             y: 0.0
-            started: 0
-            day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
-            pointID: "#{completionLineID}#{x}"
+            day: day
             values: []
+        playtimePoints[i].x = i
+        playtimePoints[i].pointID = "#{playtimeLineID}#{i}"
       @analytics.graphs[0].lines.push
         lineID: playtimeLineID
         enabled: true
@@ -187,7 +191,7 @@ module.exports = class CampaignLevelView extends CocoView
       for day, i in @analytics.levelHelps.data
         helpCount = day.alertHelps + day.paletteHelps
         started = dayStartedMap[day.day] ? 0
-        clickRate = if started > 0 then helpCount / started * 100 else -1.0
+        clickRate = if started > 0 then helpCount / started * 100 else 0
         videoRate = day.videoStarts / helpCount * 100
         helpPoints.push
           x: i
@@ -202,36 +206,30 @@ module.exports = class CampaignLevelView extends CocoView
           pointID: "#{videosLineID}#{i}"
           values: ["Help videos started: #{day.videoStarts}", "Help videos start rate: #{videoRate.toFixed(2)}%"]
       # Ensure points for each day
-      if helpPoints.length < days.length
-        for i in [1..days.length - helpPoints.length]
-          day = days[days.length - i]
-          x = helpPoints[helpPoints.length - 1].x + 1
-          helpPoints.push
-            x: x
+      for day, i in days
+        if helpPoints.length <= i or helpPoints[i].day isnt day
+          helpPoints.splice i, 0,
             y: 0.0
-            started: 0
-            day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
-            pointID: "#{helpsLineID}#{x}"
+            day: day
             values: []
-      if videoPoints.length < days.length
-        for i in [1..days.length - videoPoints.length]
-          day = days[days.length - i]
-          x = videoPoints[videoPoints.length - 1].x + 1
-          helpPoints.push
-            x: x
+        helpPoints[i].x = i
+        helpPoints[i].pointID = "#{helpsLineID}#{i}"
+        if videoPoints.length <= i or videoPoints[i].day isnt day
+          videoPoints.splice i, 0,
             y: 0.0
-            started: 0
-            day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
-            pointID: "#{videosLineID}#{x}"
+            day: day
             values: []
-      @analytics.graphs[0].lines.push
-        lineID: helpsLineID
-        enabled: true
-        points: helpPoints
-        description: lineMetadata[helpsLineID].description
-        lineColor: lineMetadata[helpsLineID].color
-        min: 0
-        max: 100.0
+        videoPoints[i].x = i
+        videoPoints[i].pointID = "#{videosLineID}#{i}"
+      if d3.max(helpPoints, (d) -> d.y) > 0
+        @analytics.graphs[0].lines.push
+          lineID: helpsLineID
+          enabled: true
+          points: helpPoints
+          description: lineMetadata[helpsLineID].description
+          lineColor: lineMetadata[helpsLineID].color
+          min: 0
+          max: 100.0
       if d3.max(videoPoints, (d) -> d.y) > 0
         @analytics.graphs[0].lines.push
           lineID: videosLineID
@@ -336,7 +334,7 @@ module.exports = class CampaignLevelView extends CocoView
           .attr("transform", "translate(" + (margin + yAxisWidth * graphLineCount) + "," + margin + ")")
           .attr("cx", (d) -> xRange(d.x))
           .attr("cy", (d) -> yRange(d.y))
-          .attr("r", (d) -> if d.started then Math.max(3, Math.min(10, Math.log(parseInt(d.started)))) else 4)
+          .attr("r", (d) -> if d.started then Math.max(3, Math.min(10, Math.log(parseInt(d.started)))) + 2 else 6)
           .attr("fill", line.lineColor)
           .attr("stroke-width", 1)
           .attr("class", "graph-point")
@@ -412,7 +410,7 @@ module.exports = class CampaignLevelView extends CocoView
       # console.log 'getLevelCompletions', data
       data.sort (a, b) -> if a.created < b.created then -1 else 1
       mapFn = (item) ->
-        item.rate = item.finished / item.started * 100
+        item.rate = if item.started > 0 then item.finished / item.started * 100 else 0
         item
       @analytics.levelCompletions.data = _.map data, mapFn, @
       doneCallback()
diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee
index 1831b5b35..aa6adeacb 100644
--- a/server/levels/level_handler.coffee
+++ b/server/levels/level_handler.coffee
@@ -358,6 +358,8 @@ LevelHandler = class LevelHandler extends Handler
     # TODO: An uncached call takes about 5s for dungeons-of-kithgard locally
     # TODO: This is very similar to getLevelCompletionsBySlugs(), time to generalize analytics APIs?
 
+    # TODO: exclude admin data
+
     levelSlugs = req.query.slugs or req.body.slugs
     startDay = req.query.startDay or req.body.startDay
     endDay = req.query.endDay or req.body.endDay