mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 08:08:15 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
f2f008e01a
12 changed files with 83 additions and 30 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -47,7 +47,10 @@ block content
|
|||
tr
|
||||
th User Start
|
||||
th Sub Start
|
||||
th
|
||||
if subscriberCancelled
|
||||
th Cancelled
|
||||
else
|
||||
th
|
||||
th
|
||||
//- th Name
|
||||
th Email
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,14 +492,18 @@ 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) ->
|
||||
done(err, document)
|
||||
query = @modelClass.findOne {slug: idOrSlug}
|
||||
query.select projection if projection
|
||||
query.exec (err, document) ->
|
||||
done(err, document)
|
||||
|
||||
doWaterfallChecks: (req, document, done) ->
|
||||
return done(null, document) unless @waterfallFunctions.length
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}"
|
||||
|
|
Loading…
Reference in a new issue