Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-04-08 16:36:56 -07:00
commit f2f008e01a
12 changed files with 83 additions and 30 deletions

View file

@ -72,10 +72,12 @@ module.exports = class LevelLoader extends CocoClass
url += "?team=#{@team}" if @team
session = new LevelSession().setURL url
session.project = ['creator', 'team', 'heroConfig', 'codeLanguage', 'submittedCodeLanguage', 'state'] if @headless
@sessionResource = @supermodel.loadModel(session, 'level_session', {cache: false})
@session = @sessionResource.model
if @opponentSessionID
opponentSession = new LevelSession().setURL "/db/level.session/#{@opponentSessionID}"
opponentSession.project = session.project if @headless
@opponentSessionResource = @supermodel.loadModel(opponentSession, 'opponent_session', {cache: false})
@opponentSession = @opponentSessionResource.model

View file

@ -447,6 +447,7 @@
managed_subs: "Managed Subscriptions"
managed_subs_desc: "Add subscriptions for other players (students, children, etc.)"
group_discounts: "Group discounts"
group_discounts_1: "We also offer group discounts for bulk subscriptions."
group_discounts_1st: "1st subscription"
group_discounts_full: "Full price"
group_discounts_2nd: "Subscriptions 2-11"
@ -611,7 +612,6 @@
how_much_2: "monthly subscription"
how_much_3: "costs $9.99, and can be cancelled anytime."
how_much_4: "Additionally, we provide discounts for larger groups:"
group_discounts_1: "We also offer group discounts for bulk subscriptions."
sys_requirements_title: "System Requirements"
sys_requirements_1: "A modern web browser. Newer versions of Chrome, Firefox, or Safari. Internet Explorer 9 or later."
sys_requirements_2: "CodeCombat is not supported on iPad yet."

View file

@ -27,6 +27,7 @@ class CocoModel extends Backbone.Model
# if fixed, RevertModal will also need the fix
setProjection: (project) ->
# TODO: ends up getting done twice, since the URL is modified and the @project is modified. So don't do this, just set project directly... (?)
return if project is @project
url = @getURL()
url += '&project=' unless /project=/.test url

View file

@ -88,7 +88,7 @@ _.extend LevelSessionSchema.properties,
type: 'boolean' # Not tracked any more
frame:
type: 'number' # Not tracked any more
thangs:
thangs: # ... what is this? Is this used?
type: 'object'
additionalProperties:
title: 'Thang'
@ -241,7 +241,7 @@ _.extend LevelSessionSchema.properties,
description: 'The date a match was computed.'
playtime:
title: 'Playtime so far'
description: 'The total seconds of playtime on this session when the match was computed.'
description: 'The total seconds of playtime on this session when the match was computed. Not currently tracked.'
type: 'number'
metrics:
type: 'object'

View file

@ -47,6 +47,9 @@ block content
tr
th User Start
th Sub Start
if subscriberCancelled
th Cancelled
else
th
th
//- th Name

View file

@ -16,9 +16,9 @@ block modal-body-content
p(data-i18n="contact.subscriber_support") Since you're a CodeCombat subscriber, your email will get our priority support.
else
p
span(data-i18n="contact.subscribe_prefix") If you need help figuring out a level, please
a.spl.spr(data-toggle="coco-modal", data-target="core/SubscribeModal", data-i18n="contact.subscribe") buy a CodeCombat subscription
span(data-i18n="contact.subscribe_suffix") and we'll be happy to help you with your code.
span.spr(data-i18n="contact.subscribe_prefix") If you need help figuring out a level, please
a(data-toggle="coco-modal", data-target="core/SubscribeModal", data-i18n="contact.subscribe") buy a CodeCombat subscription
span.spl(data-i18n="contact.subscribe_suffix") and we'll be happy to help you with your code.
.form
.form-group
label.control-label(for="contact-email", data-i18n="general.email") Email

View file

@ -3,8 +3,6 @@ template = require 'templates/admin/analytics-subscriptions'
ThangType = require 'models/ThangType'
User = require 'models/User'
# TODO: Add last N subscribers table
# TODO: Add revenue line
# TODO: Graphing code copied/mangled from campaign editor level view. OMG, DRY.
require 'vendor/d3'
@ -12,10 +10,11 @@ require 'vendor/d3'
module.exports = class AnalyticsSubscriptionsView extends RootView
id: 'admin-analytics-subscriptions-view'
template: template
targetSubCount: 2000
constructor: (options) ->
super options
@resetData()
@resetSubscriptionsData()
if me.isAdmin()
@refreshData()
_.delay (=> @refreshData()), 30 * 60 * 1000
@ -25,6 +24,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
context.analytics = @analytics ? graphs: []
context.subs = _.cloneDeep(@subs ? []).reverse()
context.subscribers = @subscribers ? []
context.subscriberCancelled = _.find context.subscribers, (subscriber) -> subscriber.cancel
context.total = @total ? 0
context.cancelled = @cancelled ? 0
context.monthlyChurn = @monthlyChurn ? 0.0
@ -35,16 +35,17 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
super()
@updateAnalyticsGraphs()
resetData: ->
resetSubscriptionsData: ->
@analytics = graphs: []
@subs = []
@total = 0
@cancelled = 0
@monthlyChurn = 0.0
@monthlyGrowth = 0.0
refreshData: ->
return unless me.isAdmin()
@resetData()
@resetSubscriptionsData()
@getSubscribers()
@getSubscriptions()
@ -75,7 +76,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
console.error 'Failed to get subscriptions', response
options.success = (subs, response, options) =>
return if @destroyed
@resetData()
@resetSubscriptionsData()
subDayMap = {}
for sub in subs
startDay = sub.start.substring(0, 10)
@ -121,6 +122,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
# TODO: Where should this metadata live?
# TODO: lineIDs assumed to be unique across graphs
totalSubsID = 'total-subs'
targetSubsID = 'target-subs'
startedSubsID = 'started-subs'
cancelledSubsID = 'cancelled-subs'
netSubsID = 'net-subs'
@ -129,6 +131,11 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
description: 'Total Active Subscriptions'
color: 'green'
strokeWidth: 1
lineMetadata[targetSubsID] =
description: 'Target Total Subscriptions'
color: 'gold'
strokeWidth: 4
opacity: 1.0
lineMetadata[startedSubsID] =
description: 'New Subscriptions'
color: 'blue'
@ -185,8 +192,9 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
points: levelPoints
description: lineMetadata[totalSubsID].description
lineColor: lineMetadata[totalSubsID].color
strokeWidth: lineMetadata[totalSubsID].strokeWidth
min: 0
max: d3.max(@subs, (d) -> d.total)
max: Math.max(@targetSubCount, d3.max(@subs, (d) -> d.total))
## Started
@ -219,9 +227,34 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
points: levelPoints
description: lineMetadata[startedSubsID].description
lineColor: lineMetadata[startedSubsID].color
strokeWidth: lineMetadata[startedSubsID].strokeWidth
min: 0
max: d3.max(@subs, (d) -> d.started)
## Total subs target
# Build line data
levelPoints = []
for sub, i in @subs
levelPoints.push
x: i
y: @targetSubCount
day: sub.day
pointID: "#{targetSubsID}#{i}"
values: []
levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays
@analytics.graphs[0].lines.push
lineID: targetSubsID
enabled: true
points: levelPoints
description: lineMetadata[targetSubsID].description
lineColor: lineMetadata[targetSubsID].color
strokeWidth: lineMetadata[targetSubsID].strokeWidth
min: 0
max: @targetSubCount
## Cancelled
averageCancelled = 0
@ -258,6 +291,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
points: levelPoints
description: lineMetadata[cancelledSubsID].description
lineColor: lineMetadata[cancelledSubsID].color
strokeWidth: lineMetadata[cancelledSubsID].strokeWidth
min: 0
max: d3.max(@subs, (d) -> d.started)
@ -333,7 +367,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
xRange = d3.scale.linear().range([0, width]).domain([d3.min(line.points, (d) -> d.x), d3.max(line.points, (d) -> d.x)])
yRange = d3.scale.linear().range([height, 0]).domain([line.min, line.max])
# x-Axis and guideline once
# x-Axis
if currentLine is 0
startDay = new Date(line.points[0].day)
endDay = new Date(line.points[line.points.length - 1].day)
@ -392,7 +426,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
svg.append("text")
.attr("x", margin + 40 + 10)
.attr("y", margin + height + xAxisHeight + keyHeight * currentLine + (keyHeight + 10) / 2)
.attr("fill", line.lineColor)
.attr("fill", if line.lineColor is 'gold' then 'orange' else line.lineColor)
.attr("class", "key-text")
.text(line.description)

View file

@ -299,7 +299,12 @@ module.exports = class CampaignView extends RootView
unless foundNext
for nextLevelOriginal in level.nextLevels
nextLevel = _.find levels, original: nextLevelOriginal
if nextLevel and not nextLevel.locked and @levelStatusMap[nextLevel.slug] isnt 'complete' and (me.isPremium() or not nextLevel.requiresSubscription or nextLevel.slug is 'apocalypse')
if nextLevel and not nextLevel.locked and @levelStatusMap[nextLevel.slug] isnt 'complete' and (
me.isPremium() or
not nextLevel.requiresSubscription or
nextLevel.slug is 'apocalypse' or
(nextLevel.slug is 'favorable-odds' and not @levelStatusMap['the-raised-sword'])
)
nextLevel.next = true
foundNext = true
break

View file

@ -19,7 +19,9 @@ module.exports = class LeaderboardModal extends ModalView
constructor: (options) ->
super options
@levelSlug = @options.levelSlug
@level = @supermodel.loadModel(new Level({_id: @levelSlug}, {project: ['name', 'i18n', 'scoreType', 'original']}), 'level').model
level = new Level({_id: @levelSlug})
level.project = ['name', 'i18n', 'scoreType', 'original']
@level = @supermodel.loadModel(level, 'level').model
getRenderData: (c) ->
c = super c

View file

@ -206,10 +206,11 @@ module.exports = class Handler
return @sendForbiddenError(res)
getById: (req, res, id) ->
# return @sendNotFoundError(res) # for testing
return @sendForbiddenError(res) unless @hasAccess(req)
@getDocumentForIdOrSlug id, (err, document) =>
if req.query.project
projection = {}
projection[field] = 1 for field in req.query.project.split(',')
@getDocumentForIdOrSlug id, projection, (err, document) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless document?
return @sendForbiddenError(res) unless @hasAccessToDocument(req, document)
@ -491,13 +492,17 @@ module.exports = class Handler
@isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24
getDocumentForIdOrSlug: (idOrSlug, done) ->
getDocumentForIdOrSlug: (idOrSlug, projection, done) ->
unless done
done = projection # projection is optional argument
projection = null
idOrSlug = idOrSlug+''
if Handler.isID(idOrSlug)
@modelClass.findById(idOrSlug).exec (err, document) ->
done(err, document)
query = @modelClass.findById(idOrSlug)
else
@modelClass.findOne {slug: idOrSlug}, (err, document) ->
query = @modelClass.findOne {slug: idOrSlug}
query.select projection if projection
query.exec (err, document) ->
done(err, document)
doWaterfallChecks: (req, document, done) ->

View file

@ -158,7 +158,8 @@ LevelHandler = class LevelHandler extends Handler
majorVersion: level.version.major
creator: req.user._id+''
query = Session.find(sessionQuery).select('-screenshot')
query = Session.find(sessionQuery).select('-screenshot -transpiledCode')
# TODO: take out "code" as well, since that can get huge containing the transpiled code for the lat hero, and find another way of having the LadderSubmissionViews in the MyMatchesTab determine rankin readiness
query.exec (err, results) =>
if err then @sendDatabaseError(res, err) else @sendSuccess res, results

View file

@ -527,11 +527,11 @@ updateMatchesInSession = (matchObject, sessionID, callback) ->
opponentsArray = _.toArray opponentsClone
currentMatchObject.opponents = opponentsArray
currentMatchObject.codeLanguage = matchObject.opponents[opponentsArray[0].sessionID].codeLanguage
currentMatchObject.simulator = @clientResponseObject.simulator
currentMatchObject.randomSeed = parseInt(@clientResponseObject.randomSeed or 0, 10)
#currentMatchObject.simulator = @clientResponseObject.simulator # Uncomment when actively debugging simulation mismatches
#currentMatchObject.randomSeed = parseInt(@clientResponseObject.randomSeed or 0, 10) # Uncomment when actively debugging simulation mismatches
LevelSession.findOne {'_id': sessionID}, (err, session) ->
session = session.toObject()
currentMatchObject.playtime = session.playtime ? 0
#currentMatchObject.playtime = session.playtime ? 0 # Not useful if we can only see last 200
sessionUpdateObject =
$push: {matches: {$each: [currentMatchObject], $slice: -200}}
#log.info "Updating session #{sessionID}"