mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 17:02:18 -05:00
This commit is contained in:
commit
4c08672dab
4 changed files with 440 additions and 109 deletions
|
@ -11,3 +11,31 @@
|
|||
|
||||
.button.close
|
||||
font-size: 63px
|
||||
|
||||
.line-graph-label
|
||||
font-size: 10pt
|
||||
font-weight: normal
|
||||
.line-graph-container
|
||||
height: 500px
|
||||
width: 100%
|
||||
position: relative
|
||||
.x.axis
|
||||
font-size: 9pt
|
||||
path
|
||||
display: none
|
||||
.y.axis
|
||||
font-size: 9pt
|
||||
path
|
||||
display: none
|
||||
.key-line
|
||||
font-size: 9pt
|
||||
.key-text
|
||||
font-size: 9pt
|
||||
.graph-point-info-container
|
||||
display: none
|
||||
position: absolute
|
||||
padding: 10px
|
||||
border: 1px solid black
|
||||
z-index: 3
|
||||
background-color: blanchedalmond
|
||||
font-size: 10pt
|
||||
|
|
|
@ -5,7 +5,7 @@ block content
|
|||
h1(data-i18n="admin.growth_title") Growth
|
||||
if me.isAdmin()
|
||||
if crunchingData
|
||||
h4 Cruncing Data..
|
||||
h4 Crunching Data..
|
||||
else
|
||||
h2 Registered Users
|
||||
h3 Per-Day
|
||||
|
|
|
@ -10,49 +10,20 @@
|
|||
input.form-control#input-startday(type='text', style='width:100px;', value=analytics.startDay)
|
||||
input.form-control#input-endday(type='text', style='width:100px;', value=analytics.endDay)
|
||||
button.btn.btn-default.btn-sm#reload-button(style='margin-left:10px;') Reload
|
||||
h4 Completion Rates
|
||||
if analytics.levelCompletions.loading
|
||||
div Loading...
|
||||
else
|
||||
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
|
||||
thead
|
||||
tr
|
||||
td Date
|
||||
td Started
|
||||
td Finished
|
||||
td Completion %
|
||||
if analytics.levelHelps.levels.length === analytics.levelCompletions.levels.length
|
||||
td Helps Clicked
|
||||
td Helps / Started
|
||||
td Help Videos
|
||||
td Videos / Started
|
||||
tbody
|
||||
- for (var i = 0; i < analytics.levelCompletions.levels.length; i++)
|
||||
tr
|
||||
td= analytics.levelCompletions.levels[i].created
|
||||
td= analytics.levelCompletions.levels[i].started
|
||||
td= analytics.levelCompletions.levels[i].finished
|
||||
td= analytics.levelCompletions.levels[i].rate
|
||||
if analytics.levelHelps.levels.length === analytics.levelCompletions.levels.length && analytics.levelCompletions.levels[i].created == analytics.levelHelps.levels[i].day
|
||||
td= analytics.levelHelps.levels[i].alertHelps + analytics.levelHelps.levels[i].paletteHelps
|
||||
td= ((analytics.levelHelps.levels[i].alertHelps + analytics.levelHelps.levels[i].paletteHelps) / analytics.levelCompletions.levels[i].started).toFixed(2)
|
||||
td= analytics.levelHelps.levels[i].videoStarts
|
||||
td= (analytics.levelHelps.levels[i].videoStarts / analytics.levelCompletions.levels[i].started).toFixed(2)
|
||||
|
||||
h4 Average Playtimes
|
||||
if analytics.levelPlaytimes.loading
|
||||
div Loading...
|
||||
else
|
||||
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
|
||||
thead
|
||||
tr
|
||||
td Date
|
||||
td Average (s)
|
||||
tbody
|
||||
- for (var i = 0; i < analytics.levelPlaytimes.levels.length; i++)
|
||||
tr
|
||||
td= analytics.levelPlaytimes.levels[i].created
|
||||
td= analytics.levelPlaytimes.levels[i].average.toFixed(2)
|
||||
each graph in analytics.graphs
|
||||
each line in graph.lines
|
||||
label.line-graph-label
|
||||
input.line-graph-checkbox(data-lineid="#{line.lineID}", type='checkbox', checked=line.enabled)
|
||||
span #{line.description}
|
||||
span
|
||||
.line-graph-container
|
||||
each line in graph.lines
|
||||
each point in line.points
|
||||
.graph-point-info-container(data-pointid="#{point.pointID}")
|
||||
div(style='font-weight:bold;') #{point.day}
|
||||
each value in point.values
|
||||
div #{value}
|
||||
|
||||
h4 Common Problems
|
||||
if analytics.commonProblems.loading
|
||||
|
@ -66,18 +37,18 @@
|
|||
td Error Hint
|
||||
td Count
|
||||
tbody
|
||||
- for (var i = 0; i < analytics.commonProblems.levels.length && i < 20; i++)
|
||||
- for (var i = 0; i < analytics.commonProblems.data.length && i < 20; i++)
|
||||
tr
|
||||
td= analytics.commonProblems.levels[i].language
|
||||
td= analytics.commonProblems.levels[i].message
|
||||
td= analytics.commonProblems.levels[i].hint
|
||||
td= analytics.commonProblems.levels[i].count
|
||||
td= analytics.commonProblems.data[i].language
|
||||
td= analytics.commonProblems.data[i].message
|
||||
td= analytics.commonProblems.data[i].hint
|
||||
td= analytics.commonProblems.data[i].count
|
||||
|
||||
h4 Recent Sessions
|
||||
if analytics.recentSessions.loading
|
||||
div Loading...
|
||||
else
|
||||
div(style='font-size:10pt') Latest #{analytics.recentSessions.levels.length} sessions for this level
|
||||
div(style='font-size:10pt') Latest #{analytics.recentSessions.data.length} sessions for this level
|
||||
div(style='font-size:10pt') Double-click row to open player and session
|
||||
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
|
||||
thead
|
||||
|
@ -89,17 +60,61 @@
|
|||
td Complete
|
||||
td Changed
|
||||
tbody
|
||||
- for (var i = 0; i < analytics.recentSessions.levels.length; i++)
|
||||
tr.recent-session(data-player-id=analytics.recentSessions.levels[i].creator, data-session-id=analytics.recentSessions.levels[i]._id)
|
||||
td= analytics.recentSessions.levels[i]._id
|
||||
td= analytics.recentSessions.levels[i].creatorName || analytics.recentSessions.levels[i].creator
|
||||
td= analytics.recentSessions.levels[i].codeLanguage
|
||||
td= analytics.recentSessions.levels[i].playtime
|
||||
if analytics.recentSessions.levels[i].state && analytics.recentSessions.levels[i].state.complete
|
||||
td= analytics.recentSessions.levels[i].state.complete
|
||||
- for (var i = 0; i < analytics.recentSessions.data.length; i++)
|
||||
tr.recent-session(data-player-id=analytics.recentSessions.data[i].creator, data-session-id=analytics.recentSessions.data[i]._id)
|
||||
td= analytics.recentSessions.data[i]._id
|
||||
td= analytics.recentSessions.data[i].creatorName || analytics.recentSessions.data[i].creator
|
||||
td= analytics.recentSessions.data[i].codeLanguage
|
||||
td= analytics.recentSessions.data[i].playtime
|
||||
if analytics.recentSessions.data[i].state && analytics.recentSessions.data[i].state.complete
|
||||
td= analytics.recentSessions.data[i].state.complete
|
||||
else
|
||||
td false
|
||||
td= analytics.recentSessions.levels[i].changed
|
||||
td= analytics.recentSessions.data[i].changed
|
||||
|
||||
h4 Completion Rates
|
||||
if analytics.levelCompletions.loading
|
||||
div Loading...
|
||||
else
|
||||
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
|
||||
thead
|
||||
tr
|
||||
td Date
|
||||
td Started
|
||||
td Finished
|
||||
td Completion %
|
||||
if analytics.levelHelps.data.length === analytics.levelCompletions.data.length
|
||||
td Helps Clicked
|
||||
td Helps / Started
|
||||
td Help Videos
|
||||
td Videos / Started
|
||||
tbody
|
||||
- for (var i = 0; i < analytics.levelCompletions.data.length; i++)
|
||||
tr
|
||||
td= analytics.levelCompletions.data[i].created
|
||||
td= analytics.levelCompletions.data[i].started
|
||||
td= analytics.levelCompletions.data[i].finished
|
||||
td= analytics.levelCompletions.data[i].rate
|
||||
if analytics.levelHelps.data.length === analytics.levelCompletions.data.length && analytics.levelCompletions.data[i].created == analytics.levelHelps.data[i].day
|
||||
td= analytics.levelHelps.data[i].alertHelps + analytics.levelHelps.data[i].paletteHelps
|
||||
td= ((analytics.levelHelps.data[i].alertHelps + analytics.levelHelps.data[i].paletteHelps) / analytics.levelCompletions.data[i].started).toFixed(2)
|
||||
td= analytics.levelHelps.data[i].videoStarts
|
||||
td= (analytics.levelHelps.data[i].videoStarts / analytics.levelCompletions.data[i].started).toFixed(2)
|
||||
|
||||
h4 Average Playtimes
|
||||
if analytics.levelPlaytimes.loading
|
||||
div Loading...
|
||||
else
|
||||
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
|
||||
thead
|
||||
tr
|
||||
td Date
|
||||
td Average (s)
|
||||
tbody
|
||||
- for (var i = 0; i < analytics.levelPlaytimes.data.length; i++)
|
||||
tr
|
||||
td= analytics.levelPlaytimes.data[i].created
|
||||
td= analytics.levelPlaytimes.data[i].average.toFixed(2)
|
||||
|
||||
if level.get('tasks')
|
||||
.tasks
|
||||
|
|
|
@ -10,16 +10,18 @@ module.exports = class CampaignLevelView extends CocoView
|
|||
template: require 'templates/editor/campaign/campaign-level-view'
|
||||
|
||||
events:
|
||||
'change .line-graph-checkbox': 'updateGraphCheckbox'
|
||||
'click .close': 'onClickClose'
|
||||
'click #reload-button': 'onClickReloadButton'
|
||||
'dblclick .recent-session': 'onDblClickRecentSession'
|
||||
'mouseenter .graph-point': 'onMouseEnterPoint'
|
||||
'mouseleave .graph-point': 'onMouseLeavePoint'
|
||||
|
||||
constructor: (options, @level) ->
|
||||
super(options)
|
||||
@fullLevel = new Level _id: @level.id
|
||||
@fullLevel.fetch()
|
||||
@listenToOnce @fullLevel, 'sync', => @render?()
|
||||
|
||||
@levelSlug = @level.get('slug')
|
||||
@getAnalytics()
|
||||
|
||||
|
@ -33,6 +35,17 @@ module.exports = class CampaignLevelView extends CocoView
|
|||
super()
|
||||
$("#input-startday").datepicker dateFormat: "yy-mm-dd"
|
||||
$("#input-endday").datepicker dateFormat: "yy-mm-dd"
|
||||
# TODO: Why does this have to be called from afterRender() instead of getRenderData()?
|
||||
@updateAnalyticsGraphs()
|
||||
|
||||
updateGraphCheckbox: (e) ->
|
||||
lineID = $(e.target).data('lineid')
|
||||
checked = $(e.target).prop('checked')
|
||||
for graph in @analytics.graphs
|
||||
for line in graph.lines
|
||||
if line.lineID is lineID
|
||||
line.enabled = checked
|
||||
return @render()
|
||||
|
||||
onClickClose: ->
|
||||
@$el.addClass('hidden')
|
||||
|
@ -51,7 +64,297 @@ module.exports = class CampaignLevelView extends CocoView
|
|||
session = new LevelSession _id: row.data 'session-id'
|
||||
@openModalView new ModelModal models: [session, player]
|
||||
|
||||
onMouseEnterPoint: (e) ->
|
||||
pointID = $(e.target).data('pointid')
|
||||
container = @$el.find(".graph-point-info-container[data-pointid=#{pointID}]").show()
|
||||
margin = 20
|
||||
width = container.outerWidth()
|
||||
height = container.outerHeight()
|
||||
container.css('left', e.offsetX - width / 2)
|
||||
container.css('top', e.offsetY - height - margin)
|
||||
|
||||
onMouseLeavePoint: (e) ->
|
||||
pointID = $(e.target).data('pointid')
|
||||
@$el.find(".graph-point-info-container[data-pointid=#{pointID}]").hide()
|
||||
|
||||
updateAnalyticsGraphData: ->
|
||||
# console.log 'updateAnalyticsGraphData'
|
||||
# Build graphs based on available @analytics data
|
||||
# Currently only one graph
|
||||
@analytics.graphs = [graphID: 'level-completions', lines: []]
|
||||
|
||||
# TODO: Where should this metadata live?
|
||||
# TODO: lineIDs assumed to be unique across graphs
|
||||
completionLineID = 'level-completions'
|
||||
playtimeLineID = 'level-playtime'
|
||||
helpsLineID = 'helps-clicked'
|
||||
videosLineID = 'help-videos'
|
||||
lineMetadata = {}
|
||||
lineMetadata[completionLineID] =
|
||||
description: 'Level Completion (%)'
|
||||
color: 'red'
|
||||
lineMetadata[playtimeLineID] =
|
||||
description: 'Average Playtime (s)'
|
||||
color: 'green'
|
||||
lineMetadata[helpsLineID] =
|
||||
description: 'Help click rate (%)'
|
||||
color: 'blue'
|
||||
lineMetadata[videosLineID] =
|
||||
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
|
||||
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 = Object.keys(days).sort (a, b) -> if a < b then -1 else 1
|
||||
|
||||
# Update level completion graph data
|
||||
dayStartedMap = {}
|
||||
if @analytics?.levelCompletions?.data?.length > 0
|
||||
# Build line data
|
||||
levelPoints = []
|
||||
for day, i in @analytics.levelCompletions.data
|
||||
dayStartedMap[day.created] = day.started
|
||||
rate = parseFloat(day.rate)
|
||||
levelPoints.push
|
||||
x: i
|
||||
y: rate
|
||||
started: day.started
|
||||
day: "#{day.created[0..3]}-#{day.created[4..5]}-#{day.created[6..7]}"
|
||||
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
|
||||
y: 0.0
|
||||
started: 0
|
||||
day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
|
||||
pointID: "#{completionLineID}#{x}"
|
||||
values: []
|
||||
@analytics.graphs[0].lines.push
|
||||
lineID: completionLineID
|
||||
enabled: true
|
||||
points: levelPoints
|
||||
description: lineMetadata[completionLineID].description
|
||||
lineColor: lineMetadata[completionLineID].color
|
||||
min: 0
|
||||
max: 100.0
|
||||
|
||||
# Update average playtime graph data
|
||||
if @analytics?.levelPlaytimes?.data?.length > 0
|
||||
# Build line data
|
||||
playtimePoints = []
|
||||
for day, i in @analytics.levelPlaytimes.data
|
||||
avg = parseFloat(day.average)
|
||||
playtimePoints.push
|
||||
x: i
|
||||
y: avg
|
||||
day: day.created
|
||||
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
|
||||
y: 0.0
|
||||
started: 0
|
||||
day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
|
||||
pointID: "#{completionLineID}#{x}"
|
||||
values: []
|
||||
@analytics.graphs[0].lines.push
|
||||
lineID: playtimeLineID
|
||||
enabled: true
|
||||
points: playtimePoints
|
||||
description: lineMetadata[playtimeLineID].description
|
||||
lineColor: lineMetadata[playtimeLineID].color
|
||||
min: 0
|
||||
max: d3.max(playtimePoints, (d) -> d.y)
|
||||
|
||||
# Update help graph data
|
||||
if @analytics?.levelHelps?.data?.length > 0
|
||||
# Build line data
|
||||
helpPoints = []
|
||||
videoPoints = []
|
||||
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
|
||||
videoRate = day.videoStarts / helpCount * 100
|
||||
helpPoints.push
|
||||
x: i
|
||||
y: clickRate
|
||||
day: "#{day.day[0..3]}-#{day.day[4..5]}-#{day.day[6..7]}"
|
||||
pointID: "#{helpsLineID}#{i}"
|
||||
values: ["Helps clicked: #{helpCount}", "Helps click clickRate: #{clickRate.toFixed(2)}%"]
|
||||
videoPoints.push
|
||||
x: i
|
||||
y: videoRate
|
||||
day: "#{day.day[0..3]}-#{day.day[4..5]}-#{day.day[6..7]}"
|
||||
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
|
||||
y: 0.0
|
||||
started: 0
|
||||
day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
|
||||
pointID: "#{helpsLineID}#{x}"
|
||||
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
|
||||
y: 0.0
|
||||
started: 0
|
||||
day: "#{day[0..3]}-#{day[4..5]}-#{day[6..7]}"
|
||||
pointID: "#{videosLineID}#{x}"
|
||||
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
|
||||
if d3.max(videoPoints, (d) -> d.y) > 0
|
||||
@analytics.graphs[0].lines.push
|
||||
lineID: videosLineID
|
||||
enabled: true
|
||||
points: videoPoints
|
||||
description: lineMetadata[videosLineID].description
|
||||
lineColor: lineMetadata[videosLineID].color
|
||||
min: 0
|
||||
max: 100.0
|
||||
|
||||
updateAnalyticsGraphs: ->
|
||||
# Build d3 graphs
|
||||
return unless @analytics?.graphs?.length > 0
|
||||
containerSelector = '.line-graph-container'
|
||||
# console.log 'updateAnalyticsGraphs', containerSelector, @analytics.graphs
|
||||
|
||||
margin = 20
|
||||
keyHeight = 20
|
||||
xAxisHeight = 20
|
||||
yAxisWidth = 40
|
||||
containerWidth = $(containerSelector).width()
|
||||
containerHeight = $(containerSelector).height()
|
||||
|
||||
for graph in @analytics.graphs
|
||||
graphLineCount = _.reduce graph.lines, ((sum, item) -> if item.enabled then sum + 1 else sum), 0
|
||||
svg = d3.select(containerSelector).append("svg")
|
||||
.attr("width", containerWidth)
|
||||
.attr("height", containerHeight)
|
||||
width = containerWidth - margin * 2 - yAxisWidth * graphLineCount
|
||||
height = containerHeight - margin * 2 - xAxisHeight - keyHeight * graphLineCount
|
||||
currentLine = 0
|
||||
for line in graph.lines
|
||||
continue unless line.enabled
|
||||
xRange = d3.scale.linear().range([0, width]).domain([d3.min(line.points, (d) -> d.x), d3.max(line.points, (d) -> d.x)])
|
||||
yRange = d3.scale.linear().range([height, 0]).domain([line.min, line.max])
|
||||
|
||||
# x-Axis and guideline once
|
||||
if currentLine is 0
|
||||
startDay = new Date(line.points[0].day)
|
||||
endDay = new Date(line.points[line.points.length - 1].day)
|
||||
xAxisRange = d3.time.scale()
|
||||
.domain([startDay, endDay])
|
||||
.range([0, width])
|
||||
xAxis = d3.svg.axis()
|
||||
.scale(xAxisRange)
|
||||
svg.append("g")
|
||||
.attr("class", "x axis")
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.attr("dy", ".35em")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * (graphLineCount - 1)) + "," + (height + margin) + ")")
|
||||
.style("text-anchor", "start")
|
||||
|
||||
# Horizontal guidelines
|
||||
svg.selectAll(".line")
|
||||
.data([10, 30, 50, 70, 90])
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("x1", margin + yAxisWidth * graphLineCount)
|
||||
.attr("y1", (d) -> margin + yRange(d))
|
||||
.attr("x2", margin + yAxisWidth * graphLineCount + width)
|
||||
.attr("y2", (d) -> margin + yRange(d))
|
||||
.attr("stroke", line.lineColor)
|
||||
.style("opacity", "0.5")
|
||||
|
||||
# y-Axis
|
||||
yAxisRange = d3.scale.linear().range([height, 0]).domain([line.min, line.max])
|
||||
yAxis = d3.svg.axis()
|
||||
.scale(yRange)
|
||||
.orient("left")
|
||||
svg.append("g")
|
||||
.attr("class", "y axis")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * currentLine) + "," + margin + ")")
|
||||
.style("color", line.lineColor)
|
||||
.call(yAxis)
|
||||
.selectAll("text")
|
||||
.attr("y", 0)
|
||||
.attr("x", 0)
|
||||
.attr("fill", line.lineColor)
|
||||
.style("text-anchor", "start")
|
||||
|
||||
# Key
|
||||
svg.append("line")
|
||||
.attr("x1", margin)
|
||||
.attr("y1", margin + height + xAxisHeight + keyHeight * currentLine + keyHeight / 2)
|
||||
.attr("x2", margin + 40)
|
||||
.attr("y2", margin + height + xAxisHeight + keyHeight * currentLine + keyHeight / 2)
|
||||
.attr("stroke", line.lineColor)
|
||||
.attr("class", "key-line")
|
||||
svg.append("text")
|
||||
.attr("x", margin + 40 + 10)
|
||||
.attr("y", margin + height + xAxisHeight + keyHeight * currentLine + (keyHeight + 10) / 2)
|
||||
.attr("fill", line.lineColor)
|
||||
.attr("class", "key-text")
|
||||
.text(line.description)
|
||||
|
||||
# Path and points
|
||||
svg.selectAll(".circle")
|
||||
.data(line.points)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.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("fill", line.lineColor)
|
||||
.attr("stroke-width", 1)
|
||||
.attr("class", "graph-point")
|
||||
.attr("data-pointid", (d) -> "#{line.lineID}#{d.x}")
|
||||
d3line = d3.svg.line()
|
||||
.x((d) -> xRange(d.x))
|
||||
.y((d) -> yRange(d.y))
|
||||
.interpolate("linear")
|
||||
svg.append("path")
|
||||
.attr("d", d3line(line.points))
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * graphLineCount) + "," + margin + ")")
|
||||
.style("stroke-width", 1)
|
||||
.style("stroke", line.lineColor)
|
||||
.style("fill", "none")
|
||||
currentLine++
|
||||
|
||||
getAnalytics: (startDay, endDay) =>
|
||||
# Analytics APIs use 2 different day formats
|
||||
if startDay?
|
||||
startDayDashed = startDay
|
||||
startDay = startDay.replace(/-/g, '')
|
||||
|
@ -61,53 +364,40 @@ module.exports = class CampaignLevelView extends CocoView
|
|||
if endDay?
|
||||
endDayDashed = endDay
|
||||
endDay = endDay.replace(/-/g, '')
|
||||
else
|
||||
else
|
||||
endDay = utils.getUTCDay -1
|
||||
endDayDashed = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
|
||||
|
||||
@analytics =
|
||||
# Initialize
|
||||
@analytics =
|
||||
startDay: startDayDashed
|
||||
endDay: endDayDashed
|
||||
commonProblems:
|
||||
levels: []
|
||||
loading: true
|
||||
levelCompletions:
|
||||
levels: []
|
||||
loading: true
|
||||
levelHelps:
|
||||
levels: []
|
||||
loading: true
|
||||
levelPlaytimes:
|
||||
levels: []
|
||||
loading: true
|
||||
recentSessions:
|
||||
levels: []
|
||||
loading: true
|
||||
@render()
|
||||
commonProblems: {data: [], loading: true}
|
||||
levelCompletions: {data: [], loading: true}
|
||||
levelHelps: {data: [], loading: true}
|
||||
levelPlaytimes: {data: [], loading: true}
|
||||
recentSessions: {data: [], loading: true}
|
||||
graphs: []
|
||||
@render() # Hide old analytics data while we fetch new data
|
||||
|
||||
@getCommonLevelProblems startDayDashed, endDayDashed, () =>
|
||||
@analytics.commonProblems.loading = false
|
||||
@render()
|
||||
@getLevelCompletions startDay, endDay, () =>
|
||||
@analytics.levelCompletions.loading = false
|
||||
@render()
|
||||
@getLevelHelps startDay, endDay, () =>
|
||||
@analytics.levelHelps.loading = false
|
||||
@render()
|
||||
@getLevelPlaytimes startDayDashed, endDayDashed, () =>
|
||||
@analytics.levelPlaytimes.loading = false
|
||||
@render()
|
||||
@getRecentSessions () =>
|
||||
@analytics.recentSessions.loading = false
|
||||
@render()
|
||||
makeFinishDataFetch = (data) =>
|
||||
return =>
|
||||
return if @destroyed
|
||||
@updateAnalyticsGraphData()
|
||||
data.loading = false
|
||||
@render()
|
||||
@getCommonLevelProblems startDayDashed, endDayDashed, makeFinishDataFetch(@analytics.commonProblems)
|
||||
@getLevelCompletions startDay, endDay, makeFinishDataFetch(@analytics.levelCompletions)
|
||||
@getLevelHelps startDay, endDay, makeFinishDataFetch(@analytics.levelHelps)
|
||||
@getLevelPlaytimes startDayDashed, endDayDashed, makeFinishDataFetch(@analytics.levelPlaytimes)
|
||||
@getRecentSessions makeFinishDataFetch(@analytics.recentSessions)
|
||||
|
||||
getCommonLevelProblems: (startDay, endDay, doneCallback) ->
|
||||
success = (data) =>
|
||||
return doneCallback() if @destroyed
|
||||
@analytics.commonProblems.levels = data
|
||||
# console.log 'getCommonLevelProblems', data
|
||||
@analytics.commonProblems.data = data
|
||||
doneCallback()
|
||||
|
||||
# TODO: Why do we need this url dash?
|
||||
request = @supermodel.addRequestResource 'common_problems', {
|
||||
url: '/db/user_code_problem/-/common_problems'
|
||||
data: {startDay: startDay, endDay: endDay, slug: @levelSlug}
|
||||
|
@ -119,13 +409,13 @@ module.exports = class CampaignLevelView extends CocoView
|
|||
getLevelCompletions: (startDay, endDay, doneCallback) ->
|
||||
success = (data) =>
|
||||
return doneCallback() if @destroyed
|
||||
data.sort (a, b) -> if a.created < b.created then 1 else -1
|
||||
mapFn = (item) ->
|
||||
item.rate = (item.finished / item.started * 100).toFixed(2)
|
||||
# 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
|
||||
@analytics.levelCompletions.levels = _.map data, mapFn, @
|
||||
@analytics.levelCompletions.data = _.map data, mapFn, @
|
||||
doneCallback()
|
||||
|
||||
request = @supermodel.addRequestResource 'level_completions', {
|
||||
url: '/db/analytics_perday/-/level_completions'
|
||||
data: {startDay: startDay, endDay: endDay, slug: @levelSlug}
|
||||
|
@ -137,9 +427,9 @@ module.exports = class CampaignLevelView extends CocoView
|
|||
getLevelHelps: (startDay, endDay, doneCallback) ->
|
||||
success = (data) =>
|
||||
return doneCallback() if @destroyed
|
||||
@analytics.levelHelps.levels = data.sort (a, b) -> if a.day < b.day then 1 else -1
|
||||
# console.log 'getLevelHelps', data
|
||||
@analytics.levelHelps.data = data.sort (a, b) -> if a.day < b.day then -1 else 1
|
||||
doneCallback()
|
||||
|
||||
request = @supermodel.addRequestResource 'level_helps', {
|
||||
url: '/db/analytics_perday/-/level_helps'
|
||||
data: {startDay: startDay, endDay: endDay, slugs: [@levelSlug]}
|
||||
|
@ -151,9 +441,9 @@ module.exports = class CampaignLevelView extends CocoView
|
|||
getLevelPlaytimes: (startDay, endDay, doneCallback) ->
|
||||
success = (data) =>
|
||||
return doneCallback() if @destroyed
|
||||
@analytics.levelPlaytimes.levels = data.sort (a, b) -> if a.created < b.created then 1 else -1
|
||||
# console.log 'getLevelPlaytimes', data
|
||||
@analytics.levelPlaytimes.data = data.sort (a, b) -> if a.created < b.created then -1 else 1
|
||||
doneCallback()
|
||||
|
||||
request = @supermodel.addRequestResource 'playtime_averages', {
|
||||
url: '/db/level/-/playtime_averages'
|
||||
data: {startDay: startDay, endDay: endDay, slugs: [@levelSlug]}
|
||||
|
@ -164,17 +454,15 @@ module.exports = class CampaignLevelView extends CocoView
|
|||
|
||||
getRecentSessions: (doneCallback) ->
|
||||
limit = 100
|
||||
|
||||
success = (data) =>
|
||||
return doneCallback() if @destroyed
|
||||
@analytics.recentSessions.levels = data
|
||||
# console.log 'getRecentSessions', data
|
||||
@analytics.recentSessions.data = data
|
||||
doneCallback()
|
||||
|
||||
# TODO: Why do we need this url dash?
|
||||
request = @supermodel.addRequestResource 'level_sessions_recent', {
|
||||
url: "/db/level_session/-/recent"
|
||||
data: {slug: @levelSlug, limit: limit}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
request.load()
|
||||
request.load()
|
||||
|
|
Loading…
Reference in a new issue