mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Update campaign editor analytics
Adding shown and purchased subscription counts. Locking down analytics to admin-only.
This commit is contained in:
parent
6b271799be
commit
e49c74259b
7 changed files with 327 additions and 96 deletions
|
@ -1,8 +1,16 @@
|
|||
#campaign-analytics-modal
|
||||
td
|
||||
font-size: 9pt
|
||||
max-width: 60px
|
||||
td.completion-rate
|
||||
max-width: 1000px
|
||||
td.level
|
||||
max-width: 1000px
|
||||
.modal-dialog
|
||||
width: 75%
|
||||
width: 85%
|
||||
.level-name-container
|
||||
position: relative
|
||||
max-width: 1000px
|
||||
.level-name-background
|
||||
position: absolute
|
||||
height: 100%
|
||||
|
@ -12,6 +20,7 @@
|
|||
opacity: 0.25
|
||||
.level-completion-container
|
||||
position: relative
|
||||
max-width: 1000px
|
||||
.level-completion-background
|
||||
position: absolute
|
||||
height: 100%
|
||||
|
|
|
@ -7,21 +7,32 @@ block modal-header-content
|
|||
input.form-control#input-startday(type='text', style='width:100px;', value=campaignCompletions.startDay)
|
||||
input.form-control#input-endday(type='text', style='width:100px;', value=campaignCompletions.endDay)
|
||||
button.btn.btn-default.btn-sm#reload-button(style='margin-left:10px;') Reload
|
||||
div(style='font-size:10px') Double-click row to open level details.
|
||||
label(style='font-size:10px;font-weight:normal;')
|
||||
span Double-click row to open level details.
|
||||
span
|
||||
input#option-show-left-game(type='checkbox', checked=showLeftGame)
|
||||
span Show Left Game
|
||||
span
|
||||
input#option-show-subscriptions(type='checkbox', checked=showSubscriptions)
|
||||
span Show Subscriptions
|
||||
|
||||
block modal-body-content
|
||||
if campaignCompletions && campaignCompletions.levels
|
||||
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
|
||||
thead
|
||||
tr
|
||||
td Level
|
||||
td.level Level
|
||||
td Started
|
||||
td Finished
|
||||
td.completion-rate Completion %
|
||||
td Playtime (s)
|
||||
if showLeftGame
|
||||
td Left Game
|
||||
td LG %
|
||||
td Playtime (s)
|
||||
td LG/s
|
||||
td Completion %
|
||||
if showSubscriptions
|
||||
td Sub Shown
|
||||
td Sub Purchased
|
||||
tbody
|
||||
- for (var i = 0; i < campaignCompletions.levels.length; i++)
|
||||
tr.level(data-level-slug=campaignCompletions.levels[i].level)
|
||||
|
@ -29,26 +40,6 @@ block modal-body-content
|
|||
span.level-name-background(style="width:#{campaignCompletions.levels[i].usersRemaining || 0}%;")
|
||||
td= campaignCompletions.levels[i].started
|
||||
td= campaignCompletions.levels[i].finished
|
||||
td= campaignCompletions.levels[i].dropped
|
||||
if campaignCompletions.levels[i].dropPercentage
|
||||
if campaignCompletions.top3DropPercentage && campaignCompletions.top3DropPercentage.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td(style='background-color:pink;')= campaignCompletions.levels[i].dropPercentage.toFixed(2)
|
||||
else
|
||||
td= campaignCompletions.levels[i].dropPercentage.toFixed(2)
|
||||
else
|
||||
td
|
||||
if campaignCompletions.levels[i].averagePlaytime
|
||||
td.level-playtime-container= campaignCompletions.levels[i].averagePlaytime.toFixed(2)
|
||||
span.level-playtime-background(style="width:#{campaignCompletions.levels[i].playtimePercentage || 0}%;")
|
||||
else
|
||||
td
|
||||
if campaignCompletions.levels[i].droppedPerSecond
|
||||
if campaignCompletions.top3DropPerSecond && campaignCompletions.top3DropPerSecond.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td(style='background-color:pink;')= campaignCompletions.levels[i].droppedPerSecond.toFixed(2)
|
||||
else
|
||||
td= campaignCompletions.levels[i].droppedPerSecond.toFixed(2)
|
||||
else
|
||||
td
|
||||
if campaignCompletions.levels[i].completionRate
|
||||
if campaignCompletions.top3 && campaignCompletions.top3.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td.level-completion-container(style='background-color:lightblue;')= campaignCompletions.levels[i].completionRate.toFixed(2)
|
||||
|
@ -59,8 +50,32 @@ block modal-body-content
|
|||
else
|
||||
td.level-completion-container= campaignCompletions.levels[i].completionRate.toFixed(2)
|
||||
svg.level-completion-background(id="background#{campaignCompletions.levels[i].level}")
|
||||
else
|
||||
td.completion-rate
|
||||
if campaignCompletions.levels[i].averagePlaytime
|
||||
td.level-playtime-container= campaignCompletions.levels[i].averagePlaytime.toFixed(2)
|
||||
span.level-playtime-background(style="width:#{campaignCompletions.levels[i].playtimePercentage || 0}%;")
|
||||
else
|
||||
td
|
||||
if showLeftGame
|
||||
td= campaignCompletions.levels[i].dropped
|
||||
if campaignCompletions.levels[i].dropPercentage
|
||||
if campaignCompletions.top3DropPercentage && campaignCompletions.top3DropPercentage.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td(style='background-color:pink;')= campaignCompletions.levels[i].dropPercentage.toFixed(2)
|
||||
else
|
||||
td= campaignCompletions.levels[i].dropPercentage.toFixed(2)
|
||||
else
|
||||
td
|
||||
if campaignCompletions.levels[i].droppedPerSecond
|
||||
if campaignCompletions.top3DropPerSecond && campaignCompletions.top3DropPerSecond.indexOf(campaignCompletions.levels[i].level) >= 0
|
||||
td(style='background-color:pink;')= campaignCompletions.levels[i].droppedPerSecond.toFixed(2)
|
||||
else
|
||||
td= campaignCompletions.levels[i].droppedPerSecond.toFixed(2)
|
||||
else
|
||||
td
|
||||
if showSubscriptions
|
||||
td= campaignCompletions.levels[i].subsShown
|
||||
td= campaignCompletions.levels[i].subsPurchased
|
||||
else
|
||||
div Loading...
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ block header
|
|||
span.glyphicon-home.glyphicon
|
||||
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
if me.isAdmin()
|
||||
li#analytics-button
|
||||
a
|
||||
span.glyphicon-stats.glyphicon
|
||||
|
|
|
@ -14,14 +14,20 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
|||
events:
|
||||
'click #reload-button': 'onClickReloadButton'
|
||||
'dblclick .level': 'onDblClickLevel'
|
||||
'change #option-show-left-game': 'updateShowLeftGame'
|
||||
'change #option-show-subscriptions': 'updateShowSubscriptions'
|
||||
|
||||
constructor: (options, @campaignHandle, @campaignCompletions) ->
|
||||
super options
|
||||
@getCampaignAnalytics() unless @campaignCompletions?.levels?
|
||||
@showLeftGame = true
|
||||
@showSubscriptions = true
|
||||
@getCampaignAnalytics() if me.isAdmin()
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.campaignCompletions = @campaignCompletions
|
||||
c.showLeftGame = @showLeftGame
|
||||
c.showSubscriptions = @showSubscriptions
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
|
@ -30,6 +36,14 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
|||
$("#input-endday").datepicker dateFormat: "yy-mm-dd"
|
||||
@addCompletionLineGraphs()
|
||||
|
||||
updateShowLeftGame: ->
|
||||
@showLeftGame = @$el.find('#option-show-left-game').prop('checked')
|
||||
@render()
|
||||
|
||||
updateShowSubscriptions: ->
|
||||
@showSubscriptions = @$el.find('#option-show-subscriptions').prop('checked')
|
||||
@render()
|
||||
|
||||
onClickReloadButton: () =>
|
||||
startDay = $('#input-startday').val()
|
||||
endDay = $('#input-endday').val()
|
||||
|
@ -45,6 +59,7 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
|||
@hide()
|
||||
|
||||
addCompletionLineGraphs: ->
|
||||
# TODO: no line graphs if some levels without completion rates?
|
||||
return unless @campaignCompletions.levels
|
||||
for level in @campaignCompletions.levels
|
||||
days = []
|
||||
|
@ -114,12 +129,14 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
|||
@render()
|
||||
@getCampaignAveragePlaytimes startDayDashed, endDayDashed, () =>
|
||||
@render()
|
||||
@getCampaignLevelSubscriptions startDay, endDay, () =>
|
||||
@render()
|
||||
|
||||
getCampaignAveragePlaytimes: (startDay, endDay, doneCallback) =>
|
||||
# Fetch level average playtimes
|
||||
# Needs date format yyyy-mm-dd
|
||||
success = (data) =>
|
||||
return if @destroyed
|
||||
return doneCallback() if @destroyed
|
||||
# console.log 'getCampaignAveragePlaytimes success', data
|
||||
levelAverages = {}
|
||||
maxPlaytime = 0
|
||||
|
@ -161,7 +178,7 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
|||
getCampaignLevelCompletions: (startDay, endDay, doneCallback) =>
|
||||
# Needs date format yyyymmdd
|
||||
success = (data) =>
|
||||
return if @destroyed
|
||||
return doneCallback() if @destroyed
|
||||
# console.log 'getCampaignLevelCompletions success', data
|
||||
countCompletions = (item) ->
|
||||
item.started = _.reduce item.days, ((result, current) -> result + current.started), 0
|
||||
|
@ -217,7 +234,7 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
|||
@campaignCompletions.top3DropPercentage = _.pluck sortedLevels[0..2], 'level'
|
||||
doneCallback()
|
||||
|
||||
return unless @campaignCompletions?.levels?
|
||||
return doneCallback() unless @campaignCompletions?.levels?
|
||||
levelSlugs = _.pluck @campaignCompletions.levels, 'level'
|
||||
|
||||
request = @supermodel.addRequestResource 'level_drops', {
|
||||
|
@ -227,3 +244,28 @@ module.exports = class CampaignAnalyticsModal extends ModalView
|
|||
success: success
|
||||
}, 0
|
||||
request.load()
|
||||
|
||||
getCampaignLevelSubscriptions: (startDay, endDay, doneCallback) =>
|
||||
# Fetch level subscriptions
|
||||
# Needs date format yyyymmdd
|
||||
success = (data) =>
|
||||
return doneCallback() if @destroyed
|
||||
# console.log 'getCampaignLevelSubscriptions success', data
|
||||
levelSubs = {}
|
||||
for item in data
|
||||
levelSubs[item.level] = shown: item.shown, purchased: item.purchased
|
||||
for level in @campaignCompletions.levels
|
||||
level.subsShown = levelSubs[level.level]?.shown
|
||||
level.subsPurchased = levelSubs[level.level]?.purchased
|
||||
doneCallback()
|
||||
|
||||
return doneCallback() unless @campaignCompletions?.levels?
|
||||
levelSlugs = _.pluck @campaignCompletions.levels, 'level'
|
||||
|
||||
request = @supermodel.addRequestResource 'campaign_subscriptions', {
|
||||
url: '/db/analytics_perday/-/level_subscriptions'
|
||||
data: {startDay: startDay, endDay: endDay, slugs: levelSlugs}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
request.load()
|
||||
|
|
|
@ -16,11 +16,81 @@
|
|||
// TODO: Are Mixpanel rates accounting for finishing steps likely to be completed in the future?
|
||||
// TODO: Use Mixpanel export API to investigate
|
||||
|
||||
// TODO: Output documents updated/inserted
|
||||
|
||||
try {
|
||||
var scriptStartTime = new Date();
|
||||
var analyticsStringCache = {};
|
||||
|
||||
// Look at last 30 days, same as Mixpanel
|
||||
var numDays = 30;
|
||||
|
||||
var startDay = new Date();
|
||||
today = startDay.toISOString().substr(0, 10);
|
||||
startDay.setUTCDate(startDay.getUTCDate() - numDays);
|
||||
startDay = startDay.toISOString().substr(0, 10);
|
||||
|
||||
var levelCompletionFunnel = ['Started Level', 'Saw Victory'];
|
||||
var levelHelpEvents = ['Problem alert help clicked', 'Spell palette help clicked', 'Start help video'];
|
||||
|
||||
log("Today is " + today);
|
||||
log("Start day is " + startDay);
|
||||
log("Funnel events are " + levelCompletionFunnel);
|
||||
|
||||
log("Getting level completion data...");
|
||||
var levelCompletionData = getLevelFunnelData(startDay, levelCompletionFunnel);
|
||||
log("Inserting aggregated level completion data...");
|
||||
for (level in levelCompletionData) {
|
||||
for (day in levelCompletionData[level]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
for (event in levelCompletionData[level][day]) {
|
||||
insertEventCount(event, level, day, levelCompletionData[level][day][event]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("Getting level drop counts...");
|
||||
var levelDropCounts = getLevelDropCounts(startDay, levelCompletionFunnel);
|
||||
log("Inserting level drop counts...");
|
||||
for (level in levelDropCounts) {
|
||||
for (day in levelDropCounts[level]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
insertEventCount('User Dropped', level, day, levelDropCounts[level][day]);
|
||||
}
|
||||
}
|
||||
|
||||
log("Getting level help counts...");
|
||||
var levelHelpCounts = getLevelHelpCounts(startDay, levelHelpEvents);
|
||||
log("Inserting level help counts...");
|
||||
for (level in levelHelpCounts) {
|
||||
for (day in levelHelpCounts[level]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
for (event in levelHelpCounts[level][day]) {
|
||||
insertEventCount(event, level, day, levelHelpCounts[level][day][event]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("Getting level subscription counts...");
|
||||
var levelSubscriptionCounts = getLevelSubscriptionCounts(startDay);
|
||||
log("Inserting level subscription counts...");
|
||||
for (level in levelSubscriptionCounts) {
|
||||
for (day in levelSubscriptionCounts[level]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
for (event in levelSubscriptionCounts[level][day]) {
|
||||
insertEventCount(event, level, day, levelSubscriptionCounts[level][day][event]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("Script runtime: " + (new Date() - scriptStartTime));
|
||||
}
|
||||
catch(err) {
|
||||
log("ERROR: " + err);
|
||||
printjson(err);
|
||||
}
|
||||
|
||||
|
||||
// *** Helper functions ***
|
||||
|
||||
function log(str) {
|
||||
print(new Date().toISOString() + " " + str);
|
||||
}
|
||||
|
@ -229,6 +299,82 @@ function getLevelHelpCounts(startDay, events) {
|
|||
return levelEventData;
|
||||
}
|
||||
|
||||
function getLevelSubscriptionCounts(startDay) {
|
||||
// Counts subscriptions shown per day, only for events that have levels
|
||||
// Subscription purchased event counts are attributed to last shown subscription modal event's day and level
|
||||
if (!startDay) return {};
|
||||
|
||||
var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
|
||||
var queryParams = {$and: [
|
||||
{_id: {$gte: startObj}},
|
||||
{$or: [
|
||||
{$and: [{'event': 'Show subscription modal'}, {'properties.level': {$exists: true}}]},
|
||||
{'event': 'Finished subscription purchase'}]
|
||||
}
|
||||
]};
|
||||
var cursor = db['analytics.log.events'].find(queryParams);
|
||||
|
||||
// Map ordering: user, event, level, day
|
||||
// Map ordering: user, event, day
|
||||
var userDataMap = {};
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var created = doc._id.getTimestamp().toISOString();
|
||||
var day = created.substring(0, 10);
|
||||
var event = doc.event;
|
||||
var user = doc.user;
|
||||
|
||||
if (!userDataMap[user]) userDataMap[user] = {};
|
||||
|
||||
if (event === 'Show subscription modal') {
|
||||
var level = doc.properties.level;
|
||||
|
||||
// TODO: This is for legacy data.
|
||||
// TODO: Event tracking updated to use level slug for loading level view on ~1/21/15
|
||||
level = level.toLowerCase().replace(/ /g, '-');
|
||||
|
||||
if (!userDataMap[user][event]) userDataMap[user][event] = {};
|
||||
if (!userDataMap[user][event][level] || userDataMap[user][event][level].localeCompare(day) > 0) {
|
||||
userDataMap[user][event][level] = day;
|
||||
}
|
||||
}
|
||||
else if (event === 'Finished subscription purchase') {
|
||||
if (!userDataMap[user][event] || userDataMap[user][event].localeCompare(day) > 0) {
|
||||
userDataMap[user][event] = day;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Data: level, day, event
|
||||
var levelFunnelData = {};
|
||||
for (user in userDataMap) {
|
||||
if (userDataMap[user]['Show subscription modal']) {
|
||||
var lastDay = null;
|
||||
var lastLevel = null;
|
||||
for (level in userDataMap[user]['Show subscription modal']) {
|
||||
var day = userDataMap[user]['Show subscription modal'][level];
|
||||
if (!lastDay || lastDay.localeCompare(day) > 0) {
|
||||
lastDay = day;
|
||||
lastLevel = level;
|
||||
}
|
||||
if (!levelFunnelData[level]) levelFunnelData[level] = {};
|
||||
if (!levelFunnelData[level][day]) levelFunnelData[level][day] = {};
|
||||
if (!levelFunnelData[level][day][event]) levelFunnelData[level][day]['Show subscription modal'] = 0;
|
||||
levelFunnelData[level][day]['Show subscription modal']++;
|
||||
}
|
||||
if (lastDay && userDataMap[user]['Finished subscription purchase']) {
|
||||
if (!levelFunnelData[lastLevel][lastDay]['Finished subscription purchase']) {
|
||||
levelFunnelData[lastLevel][lastDay]['Finished subscription purchase'] = 0;
|
||||
}
|
||||
levelFunnelData[lastLevel][lastDay]['Finished subscription purchase']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return levelFunnelData;
|
||||
}
|
||||
|
||||
function insertEventCount(event, level, day, count) {
|
||||
// analytics.perdays schema in server/analytics/AnalyticsPeryDay.coffee
|
||||
day = day.replace(/-/g, '');
|
||||
|
@ -264,60 +410,3 @@ function insertEventCount(event, level, day, count) {
|
|||
// }
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Look at last 30 days, same as Mixpanel
|
||||
var numDays = 30;
|
||||
|
||||
var startDay = new Date();
|
||||
today = startDay.toISOString().substr(0, 10);
|
||||
startDay.setUTCDate(startDay.getUTCDate() - numDays);
|
||||
startDay = startDay.toISOString().substr(0, 10);
|
||||
|
||||
var levelCompletionFunnel = ['Started Level', 'Saw Victory'];
|
||||
var levelHelpEvents = ['Problem alert help clicked', 'Spell palette help clicked', 'Start help video'];
|
||||
|
||||
log("Today is " + today);
|
||||
log("Start day is " + startDay);
|
||||
log("Funnel events are " + levelCompletionFunnel);
|
||||
|
||||
log("Getting level completion data...");
|
||||
var levelCompletionData = getLevelFunnelData(startDay, levelCompletionFunnel);
|
||||
log("Inserting aggregated level completion data...");
|
||||
for (level in levelCompletionData) {
|
||||
for (day in levelCompletionData[level]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
for (event in levelCompletionData[level][day]) {
|
||||
insertEventCount(event, level, day, levelCompletionData[level][day][event]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("Getting level drop counts...");
|
||||
var levelDropCounts = getLevelDropCounts(startDay, levelCompletionFunnel)
|
||||
log("Inserting level drop counts...");
|
||||
for (level in levelDropCounts) {
|
||||
for (day in levelDropCounts[level]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
insertEventCount('User Dropped', level, day, levelDropCounts[level][day]);
|
||||
}
|
||||
}
|
||||
|
||||
log("Getting level help counts...");
|
||||
var levelHelpCounts = getLevelHelpCounts(startDay, levelHelpEvents)
|
||||
log("Inserting level help counts...");
|
||||
for (level in levelHelpCounts) {
|
||||
for (day in levelHelpCounts[level]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
for (event in levelHelpCounts[level][day]) {
|
||||
insertEventCount(event, level, day, levelHelpCounts[level][day][event]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
log("ERROR: " + err);
|
||||
printjson(err);
|
||||
}
|
||||
|
||||
log("Script runtime: " + (new Date() - scriptStartTime));
|
|
@ -10,13 +10,15 @@ class AnalyticsPerDayHandler extends Handler
|
|||
jsonSchema: require '../../app/schemas/models/analytics_perday'
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.method in ['GET'] or req.user?.isAdmin()
|
||||
req.user?.isAdmin() or false
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
return @sendForbiddenError res unless @hasAccess req
|
||||
return @getCampaignCompletionsBySlug(req, res) if args[1] is 'campaign_completions'
|
||||
return @getLevelCompletionsBySlug(req, res) if args[1] is 'level_completions'
|
||||
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'
|
||||
super(arguments...)
|
||||
|
||||
getCampaignCompletionsBySlug: (req, res) ->
|
||||
|
@ -334,4 +336,73 @@ class AnalyticsPerDayHandler extends Handler
|
|||
@levelHelpsCache[cacheKey] = helps
|
||||
@sendSuccess res, helps
|
||||
|
||||
|
||||
getLevelSubscriptionsBySlugs: (req, res) ->
|
||||
# Send back an array of level subscriptions shown and purchased counts
|
||||
# Parameters:
|
||||
# slugs - level slugs
|
||||
# startDay - Inclusive, optional, YYYYMMDD e.g. '20141214'
|
||||
# endDay - Exclusive, optional, YYYYMMDD e.g. '20141216'
|
||||
|
||||
levelSlugs = req.query.slugs or req.body.slugs
|
||||
startDay = req.query.startDay or req.body.startDay
|
||||
endDay = req.query.endDay or req.body.endDay
|
||||
|
||||
# log.warn "level_subscriptions levelSlugs='#{levelSlugs}' startDay=#{startDay} endDay=#{endDay}"
|
||||
|
||||
return @sendSuccess res, [] unless levelSlugs?
|
||||
|
||||
# Cache results in app server memory for 1 day
|
||||
@levelSubscriptionsCache ?= {}
|
||||
@levelSubscriptionsCachedSince ?= new Date()
|
||||
if (new Date()) - @levelSubscriptionsCachedSince > 86400 * 1000
|
||||
@levelSubscriptionsCache = {}
|
||||
@levelSubscriptionsCachedSince = new Date()
|
||||
cacheKey = levelSlugs.join ''
|
||||
cacheKey += 's' + startDay if startDay?
|
||||
cacheKey += 'e' + endDay if endDay?
|
||||
return @sendSuccess res, subscriptions if subscriptions = @levelSubscriptionsCache[cacheKey]
|
||||
|
||||
findQueryParams = {v: {$in: ['Show subscription modal', 'Finished subscription purchase', 'all'].concat(levelSlugs)}}
|
||||
AnalyticsString.find(findQueryParams).exec (err, documents) =>
|
||||
if err? then return @sendDatabaseError res, err
|
||||
|
||||
levelStringIDSlugMap = {}
|
||||
for doc in documents
|
||||
showSubEventID = doc._id if doc.v is 'Show subscription modal'
|
||||
finishSubEventID = doc._id if doc.v is 'Finished subscription purchase'
|
||||
filterEventID = doc._id if doc.v is 'all'
|
||||
levelStringIDSlugMap[doc._id] = doc.v if doc.v in levelSlugs
|
||||
|
||||
return @sendSuccess res, [] unless showSubEventID? and finishSubEventID? and filterEventID?
|
||||
|
||||
queryParams = {$and: [
|
||||
{e: {$in: [showSubEventID, finishSubEventID]}},
|
||||
{f: filterEventID},
|
||||
{l: {$in: Object.keys(levelStringIDSlugMap)}}
|
||||
]}
|
||||
queryParams["$and"].push {d: {$gte: startDay}} if startDay?
|
||||
queryParams["$and"].push {d: {$lt: endDay}} if endDay?
|
||||
AnalyticsPerDay.find(queryParams).exec (err, documents) =>
|
||||
if err? then return @sendDatabaseError res, err
|
||||
|
||||
levelEventCounts = {}
|
||||
for doc in documents
|
||||
levelEventCounts[doc.l] ?= {}
|
||||
levelEventCounts[doc.l][doc.e] ?= 0
|
||||
levelEventCounts[doc.l][doc.e] += doc.c
|
||||
|
||||
subscriptions = []
|
||||
for levelID of levelEventCounts
|
||||
for eventID of levelEventCounts[levelID]
|
||||
subsShown = levelEventCounts[levelID][eventID] if parseInt(eventID) is showSubEventID
|
||||
subsPurchased = levelEventCounts[levelID][eventID] if parseInt(eventID) is finishSubEventID
|
||||
subscriptions.push
|
||||
level: levelStringIDSlugMap[levelID]
|
||||
shown: subsShown ? 0
|
||||
purchased: subsPurchased ? 0
|
||||
|
||||
@levelSubscriptionsCache[cacheKey] = subscriptions
|
||||
@sendSuccess res, subscriptions
|
||||
|
||||
module.exports = new AnalyticsPerDayHandler()
|
||||
|
|
|
@ -24,7 +24,11 @@ class UserCodeProblemHandler extends Handler
|
|||
ucp.set('creator', req.user._id)
|
||||
ucp
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.user?.isAdmin() or false
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
return @sendForbiddenError res unless @hasAccess req
|
||||
return @getCommonLevelProblemsBySlug(req, res) if args[1] is 'common_problems'
|
||||
super(arguments...)
|
||||
|
||||
|
|
Loading…
Reference in a new issue