mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-01 15:50:11 -04:00
Add 7 day net to admin subs count page
This commit is contained in:
parent
2213aff909
commit
6c1e7b9560
1 changed files with 99 additions and 35 deletions
|
@ -2,8 +2,8 @@ RootView = require 'views/core/RootView'
|
|||
template = require 'templates/admin/analytics-subscriptions'
|
||||
RealTimeCollection = require 'collections/RealTimeCollection'
|
||||
|
||||
# TODO: Add last N subscribers table
|
||||
# TODO: Add revenue line
|
||||
# TODO: Add LTV line
|
||||
# TODO: Graphing code copied/mangled from campaign editor level view. OMG, DRY.
|
||||
|
||||
require 'vendor/d3'
|
||||
|
@ -22,7 +22,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
getRenderData: ->
|
||||
context = super()
|
||||
context.analytics = @analytics ? graphs: []
|
||||
context.subs = @subs ? []
|
||||
context.subs = _.cloneDeep(@subs ? []).reverse()
|
||||
context.total = @total ? 0
|
||||
context.cancelled = @cancelled ? 0
|
||||
context.monthlyChurn = @monthlyChurn ? 0.0
|
||||
|
@ -86,6 +86,8 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
# Currently only one graph
|
||||
@analytics.graphs = [graphID: 'total-subs', lines: []]
|
||||
|
||||
timeframeDays = 60
|
||||
|
||||
return unless @subs?.length > 0
|
||||
|
||||
# TODO: Where should this metadata live?
|
||||
|
@ -93,16 +95,24 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
totalSubsID = 'total-subs'
|
||||
startedSubsID = 'started-subs'
|
||||
cancelledSubsID = 'cancelled-subs'
|
||||
netSubsID = 'net-subs'
|
||||
lineMetadata = {}
|
||||
lineMetadata[totalSubsID] =
|
||||
description: 'Total Active Subscriptions'
|
||||
color: 'green'
|
||||
strokeWidth: 1
|
||||
lineMetadata[startedSubsID] =
|
||||
description: 'New Subscriptions'
|
||||
color: 'blue'
|
||||
strokeWidth: 1
|
||||
lineMetadata[cancelledSubsID] =
|
||||
description: 'Cancelled Subscriptions'
|
||||
color: 'red'
|
||||
strokeWidth: 1
|
||||
lineMetadata[netSubsID] =
|
||||
description: '7-day Average Net Subscriptions'
|
||||
color: 'black'
|
||||
strokeWidth: 4
|
||||
|
||||
days = (sub.day for sub in @subs)
|
||||
if days.length > 0
|
||||
|
@ -139,7 +149,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
levelPoints[i].x = i
|
||||
levelPoints[i].pointID = "#{totalSubsID}#{i}"
|
||||
|
||||
levelPoints.splice(0, levelPoints.length - 60) if levelPoints.length > 60
|
||||
levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays
|
||||
|
||||
@analytics.graphs[0].lines.push
|
||||
lineID: totalSubsID
|
||||
|
@ -173,7 +183,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
levelPoints[i].x = i
|
||||
levelPoints[i].pointID = "#{startedSubsID}#{i}"
|
||||
|
||||
levelPoints.splice(0, levelPoints.length - 60) if levelPoints.length > 60
|
||||
levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays
|
||||
|
||||
@analytics.graphs[0].lines.push
|
||||
lineID: startedSubsID
|
||||
|
@ -185,16 +195,21 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
max: d3.max(@subs, (d) -> d.started)
|
||||
|
||||
## Cancelled
|
||||
averageCancelled = 0
|
||||
|
||||
# Build line data
|
||||
levelPoints = []
|
||||
cancelled = []
|
||||
for sub, i in @subs
|
||||
if i >= @subs.length - 30
|
||||
cancelled.push sub.cancelled
|
||||
levelPoints.push
|
||||
x: i
|
||||
y: sub.cancelled
|
||||
day: sub.day
|
||||
pointID: "#{cancelledSubsID}#{i}"
|
||||
values: []
|
||||
averageCancelled = cancelled.reduce((a, b) -> a + b) / cancelled.length
|
||||
|
||||
# Ensure points for each day
|
||||
for day, i in days
|
||||
|
@ -207,7 +222,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
levelPoints[i].x = i
|
||||
levelPoints[i].pointID = "#{cancelledSubsID}#{i}"
|
||||
|
||||
levelPoints.splice(0, levelPoints.length - 60) if levelPoints.length > 60
|
||||
levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays
|
||||
|
||||
@analytics.graphs[0].lines.push
|
||||
lineID: cancelledSubsID
|
||||
|
@ -218,6 +233,52 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
min: 0
|
||||
max: d3.max(@subs, (d) -> d.started)
|
||||
|
||||
## 7-Day Net Subs
|
||||
|
||||
# Build line data
|
||||
levelPoints = []
|
||||
sevenNets = []
|
||||
for sub, i in @subs
|
||||
net = 0
|
||||
if i >= @subs.length - 30
|
||||
sevenNets.push sub.started - sub.cancelled
|
||||
else
|
||||
sevenNets.push sub.started - averageCancelled
|
||||
if sevenNets.length > 7
|
||||
sevenNets.shift()
|
||||
if sevenNets.length is 7
|
||||
net = sevenNets.reduce((a, b) -> a + b) / 7
|
||||
levelPoints.push
|
||||
x: i
|
||||
y: net
|
||||
day: sub.day
|
||||
pointID: "#{netSubsID}#{i}"
|
||||
values: []
|
||||
|
||||
# Ensure points for each day
|
||||
for day, i in days
|
||||
if levelPoints.length <= i or levelPoints[i].day isnt day
|
||||
prevY = if i > 0 then levelPoints[i - 1].y else 0.0
|
||||
levelPoints.splice i, 0,
|
||||
y: prevY
|
||||
day: day
|
||||
values: []
|
||||
levelPoints[i].x = i
|
||||
levelPoints[i].pointID = "#{netSubsID}#{i}"
|
||||
|
||||
levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays
|
||||
|
||||
@analytics.graphs[0].lines.push
|
||||
lineID: netSubsID
|
||||
enabled: true
|
||||
points: levelPoints
|
||||
description: lineMetadata[netSubsID].description
|
||||
lineColor: lineMetadata[netSubsID].color
|
||||
strokeWidth: lineMetadata[netSubsID].strokeWidth
|
||||
min: 0
|
||||
max: d3.max(@subs, (d) -> d.started)
|
||||
|
||||
|
||||
updateAnalyticsGraphs: ->
|
||||
# Build d3 graphs
|
||||
return unless @analytics?.graphs?.length > 0
|
||||
|
@ -236,7 +297,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
svg = d3.select(containerSelector).append("svg")
|
||||
.attr("width", containerWidth)
|
||||
.attr("height", containerHeight)
|
||||
width = containerWidth - margin * 2 - yAxisWidth * graphLineCount
|
||||
width = containerWidth - margin * 2 - yAxisWidth * 2
|
||||
height = containerHeight - margin * 2 - xAxisHeight - keyHeight * graphLineCount
|
||||
currentLine = 0
|
||||
for line in graph.lines
|
||||
|
@ -258,36 +319,39 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.attr("dy", ".35em")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * (graphLineCount - 1)) + "," + (height + margin) + ")")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth) + "," + (height + margin) + ")")
|
||||
.style("text-anchor", "start")
|
||||
|
||||
if line.lineID is 'started-subs'
|
||||
# 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")
|
||||
marks = (Math.round(i * line.max / 5) for i in [1...5])
|
||||
svg.selectAll(".line")
|
||||
.data(marks)
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("x1", margin + yAxisWidth * 2)
|
||||
.attr("y1", (d) -> margin + yRange(d))
|
||||
.attr("x2", margin + yAxisWidth * 2 + 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")
|
||||
if currentLine < 2
|
||||
# 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")
|
||||
|
@ -309,7 +373,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
.data(line.points)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * graphLineCount) + "," + margin + ")")
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * 2) + "," + margin + ")")
|
||||
.attr("cx", (d) -> xRange(d.x))
|
||||
.attr("cy", (d) -> yRange(d.y))
|
||||
.attr("r", 2)
|
||||
|
@ -323,8 +387,8 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
.interpolate("linear")
|
||||
svg.append("path")
|
||||
.attr("d", d3line(line.points))
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * graphLineCount) + "," + margin + ")")
|
||||
.style("stroke-width", 1)
|
||||
.attr("transform", "translate(" + (margin + yAxisWidth * 2) + "," + margin + ")")
|
||||
.style("stroke-width", line.strokeWidth)
|
||||
.style("stroke", line.lineColor)
|
||||
.style("fill", "none")
|
||||
currentLine++
|
||||
|
|
Loading…
Add table
Reference in a new issue