mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
f2f307f3ad
5 changed files with 160 additions and 17 deletions
|
@ -6,9 +6,11 @@
|
|||
width: auto
|
||||
.active-classes
|
||||
color: blue
|
||||
.active-users
|
||||
.recurring-revenue
|
||||
color: green
|
||||
.active-users
|
||||
color: red
|
||||
.count
|
||||
font-size: 50pt
|
||||
font-size: 70pt
|
||||
.description
|
||||
font-size: 8pt
|
||||
|
|
|
@ -9,6 +9,10 @@ block content
|
|||
if activeClasses.length > 0
|
||||
div.description 30-day Active Classes
|
||||
div.count= activeClasses[0].groups[activeClasses[0].groups.length - 1]
|
||||
.col-md-5.big-stat.recurring-revenue
|
||||
if revenue.length > 0
|
||||
div.description 30-day Monthly Recurring Revenue
|
||||
div.count $#{Math.round((revenue[0].groups[revenue[0].groups.length - 1]) / 100)}
|
||||
.col-md-5.big-stat.active-users
|
||||
if activeUsers.length > 0
|
||||
div.description 30-day Active Users
|
||||
|
@ -26,6 +30,18 @@ block content
|
|||
each val in activeClass.groups
|
||||
td= val
|
||||
|
||||
h1 Recurring Revenue
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
th Day
|
||||
for group in revenueGroups
|
||||
th= group.replace('DRR ', '')
|
||||
each entry in revenue
|
||||
tr
|
||||
td= entry.day
|
||||
each val in entry.groups
|
||||
td $#{(val / 100).toFixed(2)}
|
||||
|
||||
h1 Active Users
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
|
|
|
@ -9,22 +9,8 @@ module.exports = class AnalyticsView extends RootView
|
|||
constructor: (options) ->
|
||||
super options
|
||||
|
||||
startDay = utils.getUTCDay(-30).replace(/-/g, '')
|
||||
endDay = utils.getUTCDay(-30).replace(/-/g, '')
|
||||
|
||||
@supermodel.addRequestResource('active_users', {
|
||||
url: '/db/analytics_perday/-/active_users'
|
||||
data: {startDay: startDay, endDay: endDay}
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
@activeUsers = data
|
||||
@activeUsers.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource('active_classes', {
|
||||
url: '/db/analytics_perday/-/active_classes'
|
||||
data: {startDay: startDay, endDay: endDay}
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
@activeClassGroups = {}
|
||||
|
@ -51,9 +37,60 @@ module.exports = class AnalyticsView extends RootView
|
|||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource('active_users', {
|
||||
url: '/db/analytics_perday/-/active_users'
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
@activeUsers = data
|
||||
@activeUsers.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource('recurring_revenue', {
|
||||
url: '/db/analytics_perday/-/recurring_revenue'
|
||||
method: 'POST'
|
||||
success: (data) =>
|
||||
@revenueGroups = {}
|
||||
dayGroupCountMap = {}
|
||||
for dailyRevenue in data
|
||||
dayGroupCountMap[dailyRevenue.day] ?= {}
|
||||
dayGroupCountMap[dailyRevenue.day]['Daily'] = 0
|
||||
for group, val of dailyRevenue.groups
|
||||
@revenueGroups[group] = true
|
||||
dayGroupCountMap[dailyRevenue.day][group] = val
|
||||
dayGroupCountMap[dailyRevenue.day]['Daily'] += val
|
||||
@revenueGroups = Object.keys(@revenueGroups)
|
||||
@revenueGroups.push 'Daily'
|
||||
@revenueGroups.push 'Monthly'
|
||||
for day of dayGroupCountMap
|
||||
for group in @revenueGroups
|
||||
dayGroupCountMap[day][group] ?= 0
|
||||
@revenue = []
|
||||
for day of dayGroupCountMap
|
||||
data = day: day, groups: []
|
||||
for group in @revenueGroups
|
||||
data.groups.push(dayGroupCountMap[day][group] ? 0)
|
||||
@revenue.push data
|
||||
@revenue.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
monthlyValues = []
|
||||
|
||||
return unless @revenue.length > 0
|
||||
|
||||
for i in [@revenue.length-1..0]
|
||||
dailyTotal = @revenue[i].groups[@revenue[i].groups.length - 2]
|
||||
monthlyValues.push(dailyTotal)
|
||||
monthlyValues.shift() if monthlyValues.length > 30
|
||||
if monthlyValues.length is 30
|
||||
monthlyIndex = @revenue[i].groups.length - 1
|
||||
@revenue[i].groups[monthlyIndex] = _.reduce(monthlyValues, (s, num) -> s + num)
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
context.activeClasses = @activeClasses ? []
|
||||
context.activeClassGroups = @activeClassGroups ? {}
|
||||
context.activeUsers = @activeUsers ? []
|
||||
context.revenue = @revenue ? []
|
||||
context.revenueGroups = @revenueGroups ? {}
|
||||
context
|
||||
|
|
|
@ -17,7 +17,7 @@ try {
|
|||
var scriptStartTime = new Date();
|
||||
var analyticsStringCache = {};
|
||||
|
||||
var numDays = 32;
|
||||
var numDays = 40;
|
||||
var daysInMonth = 30;
|
||||
|
||||
var startDay = new Date();
|
||||
|
@ -101,6 +101,17 @@ try {
|
|||
}
|
||||
}
|
||||
|
||||
log("Getting monthly recurring revenue counts...");
|
||||
var recurringRevenueCounts = getRecurringRevenueCounts(startDay);
|
||||
// printjson(recurringRevenueCounts);
|
||||
log("Inserting monthly recurring revenue counts...");
|
||||
for (var event in recurringRevenueCounts) {
|
||||
for (var day in recurringRevenueCounts[event]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
insertEventCount(event, day, recurringRevenueCounts[event][day]);
|
||||
}
|
||||
}
|
||||
|
||||
log("Script runtime: " + (new Date() - scriptStartTime));
|
||||
}
|
||||
catch(err) {
|
||||
|
@ -623,6 +634,53 @@ function getActiveClassCounts(startDay) {
|
|||
return activeClassCounts;
|
||||
}
|
||||
|
||||
function getRecurringRevenueCounts(startDay) {
|
||||
if (!startDay) return {};
|
||||
|
||||
var dailyRevenueCounts = {};
|
||||
var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
|
||||
var cursor = db.payments.find({_id: {$gte: startObj}});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var day = doc._id.getTimestamp().toISOString().substring(0, 10);
|
||||
|
||||
if (doc.service === 'ios' || doc.service === 'bitcoin') continue;
|
||||
|
||||
if (doc.productID && doc.productID.indexOf('gems_') === 0) {
|
||||
if (!dailyRevenueCounts['DRR gems']) dailyRevenueCounts['DRR gems'] = {};
|
||||
if (!dailyRevenueCounts['DRR gems'][day]) dailyRevenueCounts['DRR gems'][day] = 0;
|
||||
dailyRevenueCounts['DRR gems'][day] += doc.amount
|
||||
}
|
||||
else if (doc.productID === 'custom' || doc.service === 'external' || doc.service === 'invoice') {
|
||||
if (!dailyRevenueCounts['DRR school sales']) dailyRevenueCounts['DRR school sales'] = {};
|
||||
if (!dailyRevenueCounts['DRR school sales'][day]) dailyRevenueCounts['DRR school sales'][day] = 0;
|
||||
dailyRevenueCounts['DRR school sales'][day] += doc.amount
|
||||
}
|
||||
else if (doc.service === 'stripe' && doc.gems === 42000) {
|
||||
if (!dailyRevenueCounts['DRR yearly subs']) dailyRevenueCounts['DRR yearly subs'] = {};
|
||||
if (!dailyRevenueCounts['DRR yearly subs'][day]) dailyRevenueCounts['DRR yearly subs'][day] = 0;
|
||||
dailyRevenueCounts['DRR yearly subs'][day] += doc.amount
|
||||
}
|
||||
else if (doc.service === 'stripe') {
|
||||
// Catches prepaids, and assumes all are type terminal_subscription
|
||||
if (!dailyRevenueCounts['DRR monthly subs']) dailyRevenueCounts['DRR monthly subs'] = {};
|
||||
if (!dailyRevenueCounts['DRR monthly subs'][day]) dailyRevenueCounts['DRR monthly subs'][day] = 0;
|
||||
dailyRevenueCounts['DRR monthly subs'][day] += doc.amount
|
||||
}
|
||||
else if (doc.service === 'paypal') {
|
||||
if (!dailyRevenueCounts['DRR paypal']) dailyRevenueCounts['DRR paypal'] = {};
|
||||
if (!dailyRevenueCounts['DRR paypal'][day]) dailyRevenueCounts['DRR paypal'][day] = 0;
|
||||
dailyRevenueCounts['DRR paypal'][day] += doc.amount
|
||||
}
|
||||
// else {
|
||||
// // printjson(doc);
|
||||
// // print(doc.service, doc.amount, doc.description, JSON.stringify(doc.stripe));
|
||||
// }
|
||||
}
|
||||
|
||||
return dailyRevenueCounts;
|
||||
}
|
||||
|
||||
function insertEventCount(event, day, count) {
|
||||
// analytics.perdays schema in server/analytics/AnalyticsPeryDay.coffee
|
||||
day = day.replace(/-/g, '');
|
||||
|
|
|
@ -21,6 +21,7 @@ class AnalyticsPerDayHandler extends Handler
|
|||
return @getLevelDropsBySlugs(req, res) if args[1] is 'level_drops'
|
||||
return @getLevelHelpsBySlugs(req, res) if args[1] is 'level_helps'
|
||||
return @getLevelSubscriptionsBySlugs(req, res) if args[1] is 'level_subscriptions'
|
||||
return @getRecurringRevenue(req, res) if args[1] is 'recurring_revenue'
|
||||
super(arguments...)
|
||||
|
||||
getActiveClasses: (req, res) ->
|
||||
|
@ -465,4 +466,33 @@ class AnalyticsPerDayHandler extends Handler
|
|||
@levelSubscriptionsCache[cacheKey] = subscriptions
|
||||
@sendSuccess res, subscriptions
|
||||
|
||||
getRecurringRevenue: (req, res) ->
|
||||
events = [
|
||||
'DRR gems',
|
||||
'DRR school sales',
|
||||
'DRR yearly subs',
|
||||
'DRR monthly subs',
|
||||
'DRR paypal']
|
||||
|
||||
AnalyticsString.find({v: {$in: events}}).exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
eventIDs = []
|
||||
eventStringMap = {}
|
||||
for doc in documents
|
||||
eventStringMap[doc._id.valueOf()] = doc.v
|
||||
eventIDs.push doc._id
|
||||
return @sendSuccess res, [] unless eventIDs.length is events.length
|
||||
|
||||
AnalyticsPerDay.find({e: {$in: eventIDs}}).exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
dayCountsMap = {}
|
||||
for doc in documents
|
||||
dayCountsMap[doc.d] ?= {}
|
||||
dayCountsMap[doc.d][eventStringMap[doc.e.valueOf()]] = doc.c
|
||||
recurringRevenue = []
|
||||
for key, val of dayCountsMap
|
||||
recurringRevenue.push day: key, groups: dayCountsMap[key] ? {}
|
||||
@sendSuccess(res, recurringRevenue)
|
||||
|
||||
|
||||
module.exports = new AnalyticsPerDayHandler()
|
||||
|
|
Loading…
Reference in a new issue