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.
This commit is contained in:
Matt Lott 2015-01-26 14:58:35 -08:00
parent e41652730d
commit 019406a341
2 changed files with 51 additions and 51 deletions
app/views/editor/campaign
server/levels

View file

@ -103,12 +103,22 @@ module.exports = class CampaignLevelView extends CocoView
description: 'Help video rate (%)' description: 'Help video rate (%)'
color: 'purple' 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 = {}
days[day.created] = true for day in @analytics.levelCompletions.data if @analytics?.levelCompletions?.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.replace(/-/g, '')] = true for day in @analytics.levelPlaytimes.data if @analytics?.levelPlaytimes?.data? days[day.created] = 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.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 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 # Update level completion graph data
dayStartedMap = {} dayStartedMap = {}
@ -126,17 +136,14 @@ module.exports = class CampaignLevelView extends CocoView
pointID: "#{completionLineID}#{i}" pointID: "#{completionLineID}#{i}"
values: ["Started: #{day.started}", "Finished: #{day.finished}", "Completion rate: #{rate.toFixed(2)}%"] values: ["Started: #{day.started}", "Finished: #{day.finished}", "Completion rate: #{rate.toFixed(2)}%"]
# Ensure points for each day # Ensure points for each day
if levelPoints.length < days.length for day, i in days
for i in [1..days.length - levelPoints.length] if levelPoints.length <= i or levelPoints[i].day isnt day
day = days[days.length - i] levelPoints.splice i, 0,
x = levelPoints[levelPoints.length - 1].x + 1
levelPoints.push
x: x
y: 0.0 y: 0.0
started: 0 day: day
day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
pointID: "#{completionLineID}#{x}"
values: [] values: []
levelPoints[i].x = i
levelPoints[i].pointID = "#{completionLineID}#{i}"
@analytics.graphs[0].lines.push @analytics.graphs[0].lines.push
lineID: completionLineID lineID: completionLineID
enabled: true enabled: true
@ -159,17 +166,14 @@ module.exports = class CampaignLevelView extends CocoView
pointID: "#{playtimeLineID}#{i}" pointID: "#{playtimeLineID}#{i}"
values: ["Average playtime: #{avg.toFixed(2)}s"] values: ["Average playtime: #{avg.toFixed(2)}s"]
# Ensure points for each day # Ensure points for each day
if playtimePoints.length < days.length for day, i in days
for i in [1..days.length - playtimePoints.length] if playtimePoints.length <= i or playtimePoints[i].day isnt day
day = days[days.length - i] playtimePoints.splice i, 0,
x = playtimePoints[playtimePoints.length - 1].x + 1
playtimePoints.push
x: x
y: 0.0 y: 0.0
started: 0 day: day
day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
pointID: "#{completionLineID}#{x}"
values: [] values: []
playtimePoints[i].x = i
playtimePoints[i].pointID = "#{playtimeLineID}#{i}"
@analytics.graphs[0].lines.push @analytics.graphs[0].lines.push
lineID: playtimeLineID lineID: playtimeLineID
enabled: true enabled: true
@ -187,7 +191,7 @@ module.exports = class CampaignLevelView extends CocoView
for day, i in @analytics.levelHelps.data for day, i in @analytics.levelHelps.data
helpCount = day.alertHelps + day.paletteHelps helpCount = day.alertHelps + day.paletteHelps
started = dayStartedMap[day.day] ? 0 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 videoRate = day.videoStarts / helpCount * 100
helpPoints.push helpPoints.push
x: i x: i
@ -202,36 +206,30 @@ module.exports = class CampaignLevelView extends CocoView
pointID: "#{videosLineID}#{i}" pointID: "#{videosLineID}#{i}"
values: ["Help videos started: #{day.videoStarts}", "Help videos start rate: #{videoRate.toFixed(2)}%"] values: ["Help videos started: #{day.videoStarts}", "Help videos start rate: #{videoRate.toFixed(2)}%"]
# Ensure points for each day # Ensure points for each day
if helpPoints.length < days.length for day, i in days
for i in [1..days.length - helpPoints.length] if helpPoints.length <= i or helpPoints[i].day isnt day
day = days[days.length - i] helpPoints.splice i, 0,
x = helpPoints[helpPoints.length - 1].x + 1
helpPoints.push
x: x
y: 0.0 y: 0.0
started: 0 day: day
day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
pointID: "#{helpsLineID}#{x}"
values: [] values: []
if videoPoints.length < days.length helpPoints[i].x = i
for i in [1..days.length - videoPoints.length] helpPoints[i].pointID = "#{helpsLineID}#{i}"
day = days[days.length - i] if videoPoints.length <= i or videoPoints[i].day isnt day
x = videoPoints[videoPoints.length - 1].x + 1 videoPoints.splice i, 0,
helpPoints.push
x: x
y: 0.0 y: 0.0
started: 0 day: day
day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
pointID: "#{videosLineID}#{x}"
values: [] values: []
@analytics.graphs[0].lines.push videoPoints[i].x = i
lineID: helpsLineID videoPoints[i].pointID = "#{videosLineID}#{i}"
enabled: true if d3.max(helpPoints, (d) -> d.y) > 0
points: helpPoints @analytics.graphs[0].lines.push
description: lineMetadata[helpsLineID].description lineID: helpsLineID
lineColor: lineMetadata[helpsLineID].color enabled: true
min: 0 points: helpPoints
max: 100.0 description: lineMetadata[helpsLineID].description
lineColor: lineMetadata[helpsLineID].color
min: 0
max: 100.0
if d3.max(videoPoints, (d) -> d.y) > 0 if d3.max(videoPoints, (d) -> d.y) > 0
@analytics.graphs[0].lines.push @analytics.graphs[0].lines.push
lineID: videosLineID lineID: videosLineID
@ -336,7 +334,7 @@ module.exports = class CampaignLevelView extends CocoView
.attr("transform", "translate(" + (margin + yAxisWidth * graphLineCount) + "," + margin + ")") .attr("transform", "translate(" + (margin + yAxisWidth * graphLineCount) + "," + margin + ")")
.attr("cx", (d) -> xRange(d.x)) .attr("cx", (d) -> xRange(d.x))
.attr("cy", (d) -> yRange(d.y)) .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("fill", line.lineColor)
.attr("stroke-width", 1) .attr("stroke-width", 1)
.attr("class", "graph-point") .attr("class", "graph-point")
@ -412,7 +410,7 @@ module.exports = class CampaignLevelView extends CocoView
# console.log 'getLevelCompletions', data # console.log 'getLevelCompletions', data
data.sort (a, b) -> if a.created < b.created then -1 else 1 data.sort (a, b) -> if a.created < b.created then -1 else 1
mapFn = (item) -> mapFn = (item) ->
item.rate = item.finished / item.started * 100 item.rate = if item.started > 0 then item.finished / item.started * 100 else 0
item item
@analytics.levelCompletions.data = _.map data, mapFn, @ @analytics.levelCompletions.data = _.map data, mapFn, @
doneCallback() doneCallback()

View file

@ -358,6 +358,8 @@ LevelHandler = class LevelHandler extends Handler
# TODO: An uncached call takes about 5s for dungeons-of-kithgard locally # 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: This is very similar to getLevelCompletionsBySlugs(), time to generalize analytics APIs?
# TODO: exclude admin data
levelSlugs = req.query.slugs or req.body.slugs levelSlugs = req.query.slugs or req.body.slugs
startDay = req.query.startDay or req.body.startDay startDay = req.query.startDay or req.body.startDay
endDay = req.query.endDay or req.body.endDay endDay = req.query.endDay or req.body.endDay