mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-05-03 01:14:46 -04:00
Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
a2554570b0
4 changed files with 228 additions and 0 deletions
app
|
@ -31,6 +31,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'admin/clas': go('admin/CLAsView')
|
'admin/clas': go('admin/CLAsView')
|
||||||
'admin/employers': go('admin/EmployersListView')
|
'admin/employers': go('admin/EmployersListView')
|
||||||
'admin/files': go('admin/FilesView')
|
'admin/files': go('admin/FilesView')
|
||||||
|
'admin/growth': go('admin/GrowthView')
|
||||||
'admin/level-sessions': go('admin/LevelSessionsView')
|
'admin/level-sessions': go('admin/LevelSessionsView')
|
||||||
'admin/users': go('admin/UsersView')
|
'admin/users': go('admin/UsersView')
|
||||||
'admin/base': go('admin/BaseView')
|
'admin/base': go('admin/BaseView')
|
||||||
|
|
|
@ -43,6 +43,9 @@ block content
|
||||||
a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade)
|
a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade)
|
||||||
li
|
li
|
||||||
a(href="/admin/clas", data-i18n="admin.clas") CLAs
|
a(href="/admin/clas", data-i18n="admin.clas") CLAs
|
||||||
|
if me.isAdmin()
|
||||||
|
li
|
||||||
|
a(href="/admin/growth", data-i18n="admin.growth") Growth
|
||||||
|
|
||||||
hr
|
hr
|
||||||
|
|
||||||
|
|
32
app/templates/admin/growth.jade
Normal file
32
app/templates/admin/growth.jade
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
extends /templates/base
|
||||||
|
|
||||||
|
block content
|
||||||
|
|
||||||
|
h1(data-i18n="admin.growth_title") Growth
|
||||||
|
if me.isAdmin()
|
||||||
|
if crunchingData
|
||||||
|
h4 Cruncing Data..
|
||||||
|
else
|
||||||
|
h2 Registered Users
|
||||||
|
h3 Per-Day
|
||||||
|
h4 Totals
|
||||||
|
svg.perDayTotal
|
||||||
|
h4 Added
|
||||||
|
svg.perDayAdded
|
||||||
|
table.table.table-striped.table-bordered.table-condensed
|
||||||
|
-for (var i = 0; i < usersPerDay.length; i++)
|
||||||
|
tr
|
||||||
|
td= usersPerDay[i].date
|
||||||
|
td= usersPerDay[i].added
|
||||||
|
td= usersPerDay[i].total
|
||||||
|
h3 Per-Month
|
||||||
|
h4 Totals
|
||||||
|
svg.perMonthTotal
|
||||||
|
h4 Added
|
||||||
|
svg.perMonthAdded
|
||||||
|
table.table.table-striped.table-bordered.table-condensed
|
||||||
|
-for (var i = 0; i < usersPerMonth.length; i++)
|
||||||
|
tr
|
||||||
|
td= usersPerMonth[i].date
|
||||||
|
td= usersPerMonth[i].added
|
||||||
|
td= usersPerMonth[i].total
|
192
app/views/admin/GrowthView.coffee
Normal file
192
app/views/admin/GrowthView.coffee
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
RootView = require 'views/kinds/RootView'
|
||||||
|
template = require 'templates/admin/growth'
|
||||||
|
RealTimeCollection = require 'collections/RealTimeCollection'
|
||||||
|
|
||||||
|
# Growth View ###################
|
||||||
|
#
|
||||||
|
# Display interesting growth data.
|
||||||
|
#
|
||||||
|
# Currently shows:
|
||||||
|
# Registered user totals and added, per-day and per-month
|
||||||
|
# 7-day moving average for registered users added per-day
|
||||||
|
#
|
||||||
|
# TODO: @padding isn't applied correctly
|
||||||
|
# TODO: aggregate recent data if missing?
|
||||||
|
#
|
||||||
|
|
||||||
|
module.exports = class GrowthView extends RootView
|
||||||
|
id: 'admin-growth-view'
|
||||||
|
template: template
|
||||||
|
height: 300
|
||||||
|
width: 1000
|
||||||
|
xAxisGuideHeight: 80
|
||||||
|
yAxisGuideWidth: 60
|
||||||
|
padding: 10
|
||||||
|
|
||||||
|
constructor: (options) ->
|
||||||
|
super options
|
||||||
|
@usersPerMonth = new RealTimeCollection 'growth/users/registered/per-month'
|
||||||
|
@usersPerMonth.on 'add', @refreshData
|
||||||
|
@usersPerDay = new RealTimeCollection 'growth/users/registered/per-day'
|
||||||
|
@usersPerDay.on 'add', @refreshData
|
||||||
|
|
||||||
|
destroy: ->
|
||||||
|
@usersPerMonth.off 'add', @refreshData
|
||||||
|
@usersPerDay.off 'add', @refreshData
|
||||||
|
|
||||||
|
refreshData: =>
|
||||||
|
@render()
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
c = super()
|
||||||
|
c.crunchingData = @usersPerMonth.length is 0 and @usersPerDay.length is 0
|
||||||
|
c.usersPerDay = []
|
||||||
|
# @usersPerDay.each (item) ->
|
||||||
|
# c.usersPerDay.push date: item.get('id'), added: item.get('added'), total: item.get('total')
|
||||||
|
c.usersPerMonth = []
|
||||||
|
# @usersPerMonth.each (item) ->
|
||||||
|
# c.usersPerMonth.push date: item.get('id'), added: item.get('added'), total: item.get('total')
|
||||||
|
c
|
||||||
|
|
||||||
|
afterRender: ->
|
||||||
|
super()
|
||||||
|
if me.isAdmin()
|
||||||
|
@createPerDayChart()
|
||||||
|
@createPerMonthChart()
|
||||||
|
|
||||||
|
createPerDayChart: ->
|
||||||
|
addedData = []
|
||||||
|
totalData = []
|
||||||
|
@usersPerDay.each (item) ->
|
||||||
|
addedData.push id: item.get('id'), value: item.get('added')
|
||||||
|
totalData.push id: item.get('id'), value: item.get('total')
|
||||||
|
@createLineChart ".perDayTotal", totalData, 1000
|
||||||
|
@createLineChart ".perDayAdded", addedData, 10, true
|
||||||
|
|
||||||
|
createPerMonthChart: ->
|
||||||
|
addedData = []
|
||||||
|
totalData = []
|
||||||
|
@usersPerMonth.each (item) ->
|
||||||
|
addedData.push id: item.get('id'), value: item.get('added')
|
||||||
|
totalData.push id: item.get('id'), value: item.get('total')
|
||||||
|
@createLineChart ".perMonthTotal", totalData, 1000
|
||||||
|
@createLineChart ".perMonthAdded", addedData, 1000
|
||||||
|
|
||||||
|
createLineChart: (selector, data, guidelineSpacing, sevenDayAverage=false) ->
|
||||||
|
return unless data.length > 1
|
||||||
|
|
||||||
|
minVal = d3.min(data, (d) -> d.value)
|
||||||
|
maxVal = d3.max(data, (d) -> d.value)
|
||||||
|
|
||||||
|
widthSpacing = (@width - @yAxisGuideWidth - @padding) / (data.length - 1)
|
||||||
|
|
||||||
|
y = d3.scale.linear()
|
||||||
|
.domain([minVal, maxVal])
|
||||||
|
.range([@height - @xAxisGuideHeight - 2 * @padding, 0])
|
||||||
|
|
||||||
|
points = []
|
||||||
|
for i in [0...data.length]
|
||||||
|
points.push id: data[i].id, x: i * widthSpacing + @yAxisGuideWidth, y: y(data[i].value) + @padding
|
||||||
|
|
||||||
|
links = []
|
||||||
|
for i in [0...points.length - 1]
|
||||||
|
if points[i] and points[i + 1]
|
||||||
|
links.push start: points[i], end: points[i + 1]
|
||||||
|
|
||||||
|
guidelines = []
|
||||||
|
diff = maxVal - minVal
|
||||||
|
interval = Math.floor(diff / 5)
|
||||||
|
for i in [0..4]
|
||||||
|
yVal = i * interval + minVal
|
||||||
|
yVal = Math.floor(yVal / guidelineSpacing) * guidelineSpacing
|
||||||
|
guidelines.push start: {id: yVal, x: 0, y: y(yVal)}, end: {id: yVal, x: @width, y: y(yVal)}
|
||||||
|
|
||||||
|
sevenPoints = []
|
||||||
|
sevenLinks = []
|
||||||
|
if sevenDayAverage
|
||||||
|
sevenTotal = 0
|
||||||
|
for i in [0...data.length]
|
||||||
|
sevenTotal += data[i].value
|
||||||
|
if i > 5
|
||||||
|
sevenAvg = sevenTotal / 7
|
||||||
|
sevenPoints.push x: i * widthSpacing + @yAxisGuideWidth, y: y(sevenAvg) + @padding
|
||||||
|
if i > 6
|
||||||
|
sevenTotal -= data[i - 7].value
|
||||||
|
for i in [0...sevenPoints.length - 1]
|
||||||
|
if sevenPoints[i] and sevenPoints[i + 1]
|
||||||
|
sevenLinks.push start: sevenPoints[i], end: sevenPoints[i + 1]
|
||||||
|
|
||||||
|
chart = d3.select(selector)
|
||||||
|
.attr("width", @width)
|
||||||
|
.attr("height", @height)
|
||||||
|
|
||||||
|
chart.selectAll(".circle")
|
||||||
|
.data(points)
|
||||||
|
.enter()
|
||||||
|
.append("circle")
|
||||||
|
.attr("cx", (d) -> d.x )
|
||||||
|
.attr("cy", (d) -> d.y )
|
||||||
|
.attr("r", "2px")
|
||||||
|
.attr("fill", "black")
|
||||||
|
|
||||||
|
chart.selectAll(".text")
|
||||||
|
.data(points)
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.attr("transform", (d, i) => "translate(" + d.x + "," + @height + ") rotate(270)")
|
||||||
|
.text((d) ->
|
||||||
|
if d.id.length is 8
|
||||||
|
return "#{parseInt(d.id[4..5])}/#{parseInt(d.id[6..7])}/#{d.id[0..3]}"
|
||||||
|
else
|
||||||
|
return "#{parseInt(d.id[4..5])}/#{d.id[0..3]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
chart.selectAll('.line')
|
||||||
|
.data(links)
|
||||||
|
.enter()
|
||||||
|
.append("line")
|
||||||
|
.attr("x1", (d) -> d.start.x )
|
||||||
|
.attr("y1", (d) -> d.start.y )
|
||||||
|
.attr("x2", (d) -> d.end.x )
|
||||||
|
.attr("y2", (d) -> d.end.y )
|
||||||
|
.style("stroke", "rgb(6,120,155)")
|
||||||
|
|
||||||
|
chart.selectAll(".circle")
|
||||||
|
.data(sevenPoints)
|
||||||
|
.enter()
|
||||||
|
.append("circle")
|
||||||
|
.attr("cx", (d) -> d.x )
|
||||||
|
.attr("cy", (d) -> d.y )
|
||||||
|
.attr("r", "2px")
|
||||||
|
.attr("fill", "purple")
|
||||||
|
|
||||||
|
chart.selectAll('.line')
|
||||||
|
.data(sevenLinks)
|
||||||
|
.enter()
|
||||||
|
.append("line")
|
||||||
|
.attr("x1", (d) -> d.start.x )
|
||||||
|
.attr("y1", (d) -> d.start.y )
|
||||||
|
.attr("x2", (d) -> d.end.x )
|
||||||
|
.attr("y2", (d) -> d.end.y )
|
||||||
|
.style("stroke", "rgb(200,0,0)")
|
||||||
|
|
||||||
|
chart.selectAll('.line')
|
||||||
|
.data(guidelines)
|
||||||
|
.enter()
|
||||||
|
.append("line")
|
||||||
|
.attr("x1", (d) -> d.start.x )
|
||||||
|
.attr("y1", (d) -> d.start.y )
|
||||||
|
.attr("x2", (d) -> d.end.x )
|
||||||
|
.attr("y2", (d) -> d.end.y )
|
||||||
|
.style("stroke", "rgb(140,140,140)")
|
||||||
|
|
||||||
|
chart.selectAll(".text")
|
||||||
|
.data(guidelines)
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
.attr("x", (d) -> d.start.x)
|
||||||
|
.attr("y", (d) -> d.start.y - 6)
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.text((d) -> d.start.id)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue