Add recent subscribers to admin dashboard

This commit is contained in:
Matt Lott 2015-04-07 15:00:50 -07:00
parent 603f9f9a37
commit 3c755d39e6
4 changed files with 139 additions and 18 deletions

View file

@ -26,9 +26,6 @@
height: 500px
width: 100%
// TODO: figure out why this is necessary
margin-bottom: 100px
.x.axis
font-size: 9pt
path

View file

@ -1,12 +1,13 @@
extends /templates/base
block content
if !me.isAdmin()
div You must be logged in as an admin to view this page.
else
if total === 0
h1 Fetching subscriptions data...
h4 Fetching dashboard data...
else
.container-fluid
.row
@ -37,15 +38,56 @@ block content
div *Stripe APIs do not return information about inactive subs.
br
table.table.table-condensed.concepts-table
h2 Recent Subscribers
if !subscribers || subscribers.length < 1
h4 Fetching recent subscribers...
else
table.table.table-condensed
thead
tr
th User Start
th Sub Start
th
th
//- th Name
th Email
th Hero
th Level
th Last Level
th Age
th Spoken
tbody
each subscriber in subscribers
tr
td= subscriber.user.dateCreated.substring(0, 10)
td= subscriber.start.substring(0, 10)
td
if subscriber.cancel
span= subscriber.cancel.substring(0, 10)
td
if subscriber.user.stripe.sponsorID
span Sponsored
//- td
//- a(href="/user/#{subscriber.user._id}")= subscriber.user.name || 'Anoner'
td= subscriber.user.emailLower
td= subscriber.hero
td= subscriber.level
td= subscriber.user.lastLevel
td= subscriber.user.ageRange
td= subscriber.user.preferredLanguage
h2 Subscriptions
if !subs || subs.length < 1
h4 Fetching subscriptions...
else
table.table.table-condensed
thead
tr
th Day
th Total
th Started
th Cancelled
th Net
tbody
each sub in subs
tr
@ -53,3 +95,4 @@ block content
td= sub.total
td= sub.started
td= sub.cancelled
td= sub.started - sub.cancelled

View file

@ -1,6 +1,7 @@
RootView = require 'views/core/RootView'
template = require 'templates/admin/analytics-subscriptions'
RealTimeCollection = require 'collections/RealTimeCollection'
ThangType = require 'models/ThangType'
User = require 'models/User'
# TODO: Add last N subscribers table
# TODO: Add revenue line
@ -23,6 +24,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
context = super()
context.analytics = @analytics ? graphs: []
context.subs = _.cloneDeep(@subs ? []).reverse()
context.subscribers = @subscribers ? []
context.total = @total ? 0
context.cancelled = @cancelled ? 0
context.monthlyChurn = @monthlyChurn ? 0.0
@ -43,7 +45,28 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
refreshData: ->
return unless me.isAdmin()
@resetData()
@getSubscribers()
@getSubscriptions()
getSubscribers: ->
options =
url: '/db/subscription/-/subscribers'
method: 'POST'
data: {maxCount: 30}
options.error = (model, response, options) =>
return if @destroyed
console.error 'Failed to get subscribers', response
options.success = (subscribers, response, options) =>
return if @destroyed
@subscribers = subscribers
for subscriber in @subscribers
subscriber.level = User.levelFromExp subscriber.user.points
if hero = subscriber.user.heroConfig?.thangType
subscriber.hero = slug for slug, original of ThangType.heroes when original is hero
@render?()
@supermodel.addRequestResource('get_subscribers', options, 0).load()
getSubscriptions: ->
options =
url: '/db/subscription/-/subscriptions'
method: 'GET'
@ -66,7 +89,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
for day of subDayMap
@subs.push
day: day
started: subDayMap[day]['start']
started: subDayMap[day]['start'] or 0
cancelled: subDayMap[day]['cancel'] or 0
@subs.sort (a, b) -> a.day.localeCompare(b.day)
@ -78,9 +101,9 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
startedLastMonth += sub.started if @subs.length - i < 31
@monthlyChurn = @cancelled / startedLastMonth * 100.0 if startedLastMonth > 0
if @subs.length > 30 and @subs[@subs.length - 31].total > 0
lastMonthTotal = @subs[@subs.length - 31].total
thisMonthTotal = @subs[@subs.length - 1].total
@monthlyGrowth = (thisMonthTotal - lastMonthTotal) / lastMonthTotal * 100
startMonthTotal = @subs[@subs.length - 31].total
endMonthTotal = @subs[@subs.length - 1].total
@monthlyGrowth = (endMonthTotal / startMonthTotal - 1) * 100
@updateAnalyticsGraphData()
@render?()
@supermodel.addRequestResource('get_subscriptions', options, 0).load()

View file

@ -1,6 +1,7 @@
# Not paired with a document in the DB, just handles coordinating between
# the stripe property in the user with what's being stored in Stripe.
mongoose = require 'mongoose'
async = require 'async'
config = require '../../server_config'
Handler = require '../commons/Handler'
@ -23,9 +24,70 @@ class SubscriptionHandler extends Handler
console.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'"
getByRelationship: (req, res, args...) ->
return @getSubscribers(req, res) if args[1] is 'subscribers'
return @getSubscriptions(req, res) if args[1] is 'subscriptions'
super(arguments...)
getSubscribers: (req, res) ->
return @sendForbiddenError(res) unless req.user and req.user.isAdmin()
maxReturnCount = req.body.maxCount or 20
# @subscribers ?= []
# return @sendSuccess(res, @subscribers) unless _.isEmpty(@subscribers)
@subscribers = []
subscriberIDs = []
customersProcessed = 0
nextBatch = (starting_after, done) =>
options = limit: 100
options.starting_after = starting_after if starting_after
stripe.customers.list options, (err, customers) =>
return done(err) if err
customersProcessed += customers.data.length
for customer in customers.data
break unless @subscribers.length < maxReturnCount
continue unless customer?.subscriptions?.data?.length > 0
for subscription in customer.subscriptions.data
continue unless subscription.plan.id is 'basic'
amount = subscription.plan.amount
if subscription?.discount?.coupon?
if subscription.discount.coupon.percent_off
amount = amount * (100 - subscription.discount.coupon.percent_off) / 100;
else if subscription.discount.coupon.amount_off
amount -= subscription.discount.coupon.amount_off
else if customer.discount?.coupon?
if customer.discount.coupon.percent_off
amount = amount * (100 - customer.discount.coupon.percent_off) / 100
else if customer.discount.coupon.amount_off
amount -= customer.discount.coupon.amount_off
continue unless amount > 0
subscriber = start: new Date(subscription.start * 1000)
if subscription.metadata?.id?
subscriber.userID = subscription.metadata.id
subscriberIDs.push subscription.metadata.id
if subscription.cancel_at_period_end
subscriber.cancel = new Date(subscription.canceled_at * 1000)
subscriber.end = new Date(subscription.current_period_end * 1000)
@subscribers.push(subscriber)
if customers.has_more and @subscribers.length < maxReturnCount
return nextBatch(customers.data[customers.data.length - 1].id, done)
else
return done()
nextBatch null, (err) =>
return @sendDatabaseError(res, err) if err
User.find {_id: {$in: subscriberIDs}}, (err, users) =>
return @sendDatabaseError(res, err) if err
for user in users
subscriber.user = user for subscriber in @subscribers when subscriber.userID is user.id
@sendSuccess(res, @subscribers)
getSubscriptions: (req, res) ->
# Returns a list of active subscriptions
# TODO: does not handle customers with 11+ active subscriptions
@ -54,7 +116,6 @@ class SubscriptionHandler extends Handler
for subscription in customer.subscriptions.data
continue unless subscription.plan.id is 'basic'
amount = subscription.plan.amount
if subscription?.discount?.coupon?
if subscription.discount.coupon.percent_off
@ -72,7 +133,7 @@ class SubscriptionHandler extends Handler
sub = start: new Date(subscription.start * 1000)
if subscription.cancel_at_period_end
sub.cancel = new Date(subscription.canceled_at * 1000)
sub.end = new Date(sub.current_period_end * 1000)
sub.end = new Date(subscription.current_period_end * 1000)
@subs.push(sub)
# Can't fetch all the test Stripe data
@ -84,9 +145,6 @@ class SubscriptionHandler extends Handler
return @sendDatabaseError(res, err) if err
@sendSuccess(res, @subs)
subscribeUser: (req, user, done) ->
if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
return done({res: 'You must be signed in to subscribe.', code: 403})