mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-13 01:01:34 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
5a98926d52
27 changed files with 118 additions and 267 deletions
|
@ -56,6 +56,7 @@ so we can accept your pull requests. It is easy.
|
|||
![Josh Callebaut](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Josh%20Callebaut/josh_callebaut_100.png "Josh Callebaut")
|
||||
![Michael Schmatz](http://codecombat.com/images/pages/about/michael_small.png "Michael Schmatz")
|
||||
![Josh Lee](http://codecombat.com/images/pages/about/josh_small.png "Josh Lee")
|
||||
![Dan TDM](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Dan_TDM/dan_tdm_100.png "Dan TDM")
|
||||
![Alex Cotsarelis](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Alex%20Cotsarelis/alex_100.png "Alex Cotsarelis")
|
||||
![Alex Crooks](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Alex%20Crooks/alex_100.png "Alex Crooks")
|
||||
![Alexandru Caciulescu](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Alexandru%20Caciulescu/alexandru_100.png "Alexandru Caciulescu")
|
||||
|
|
|
@ -5,12 +5,6 @@ module.exports = class LevelSessionCollection extends CocoCollection
|
|||
url: '/db/level.session'
|
||||
model: LevelSession
|
||||
|
||||
fetchMineForCourseInstance: (courseInstanceID, options) ->
|
||||
options = _.extend({
|
||||
url: "/db/course_instance/#{courseInstanceID}/my-course-level-sessions"
|
||||
}, options)
|
||||
@fetch(options)
|
||||
|
||||
fetchForCourseInstance: (courseInstanceID, options) ->
|
||||
options = _.extend({
|
||||
url: "/db/course_instance/#{courseInstanceID}/my-course-level-sessions"
|
||||
|
|
|
@ -478,7 +478,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@world.difficulty = @session?.get('state')?.difficulty ? 0
|
||||
if @observing
|
||||
@world.difficulty = Math.max 0, @world.difficulty - 1 # Show the difficulty they won, not the next one.
|
||||
serializedLevel = @level.serialize(@supermodel, @session, @opponentSession)
|
||||
serializedLevel = @level.serialize {@supermodel, @session, @opponentSession, @headless, @sessionless}
|
||||
@world.loadFromLevel serializedLevel, false
|
||||
console.log 'World has been initialized from level loader.' if LOG
|
||||
|
||||
|
|
|
@ -226,7 +226,7 @@ module.exports = class Simulator extends CocoClass
|
|||
@levelLoader = null
|
||||
|
||||
setupGod: ->
|
||||
@god.setLevel @level.serialize(@supermodel, @session, @otherSession)
|
||||
@god.setLevel @level.serialize {@supermodel, @session, @otherSession, headless: true, sessionless: false}
|
||||
@god.setLevelSessionIDs (session.sessionID for session in @task.getSessions())
|
||||
@god.setWorldClassMap @world.classMap
|
||||
@god.setGoalManager new GoalManager @world, @level.get('goals'), null, {headless: true}
|
||||
|
|
|
@ -12,7 +12,8 @@ module.exports = class Level extends CocoModel
|
|||
urlRoot: '/db/level'
|
||||
editableByArtisans: true
|
||||
|
||||
serialize: (supermodel, session, otherSession, cached=false) ->
|
||||
serialize: (options) ->
|
||||
{supermodel, session, otherSession, @headless, @sessionless, cached=false} = options
|
||||
o = @denormalize supermodel, session, otherSession # hot spot to optimize
|
||||
|
||||
# Figure out Components
|
||||
|
@ -146,7 +147,7 @@ module.exports = class Level extends CocoModel
|
|||
levelThang.components.push placeholderComponent
|
||||
|
||||
# Load the user's chosen hero AFTER getting stats from default char
|
||||
if /Hero Placeholder/.test(levelThang.id) and @get('type', true) in ['course']
|
||||
if /Hero Placeholder/.test(levelThang.id) and @get('type', true) in ['course'] and not @headless and not @sessionless
|
||||
heroThangType = me.get('heroConfig')?.thangType or ThangType.heroes.captain
|
||||
levelThang.thangType = heroThangType if heroThangType
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ _.extend LevelSessionSchema.properties,
|
|||
changed: c.date
|
||||
title: 'Changed'
|
||||
readOnly: true
|
||||
|
||||
|
||||
dateFirstCompleted: {} # c.stringDate
|
||||
# title: 'Completed'
|
||||
# readOnly: true
|
||||
|
@ -62,9 +62,6 @@ _.extend LevelSessionSchema.properties,
|
|||
team: c.shortString()
|
||||
level: LevelSessionLevelSchema
|
||||
|
||||
screenshot:
|
||||
type: 'string'
|
||||
|
||||
heroConfig: c.HeroConfigSchema
|
||||
|
||||
state: c.object {},
|
||||
|
@ -208,14 +205,6 @@ _.extend LevelSessionSchema.properties,
|
|||
submittedCodeLanguage:
|
||||
type: 'string'
|
||||
|
||||
transpiledCode:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
type: 'string'
|
||||
format: 'code'
|
||||
|
||||
isRanking:
|
||||
type: 'boolean'
|
||||
description: 'Whether this session is still in the first ranking chain after being submitted.'
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
#admin-level-sessions-view
|
||||
.session_tile
|
||||
display: inline-block
|
||||
position: relative
|
||||
margin: 8px
|
||||
|
||||
.session_info
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
right: 0
|
||||
text-align: center
|
||||
background: rgba(0, 0, 0, 0.5)
|
||||
color: white
|
|
@ -30,8 +30,6 @@ block content
|
|||
h4 Entities
|
||||
|
||||
ul
|
||||
li
|
||||
a(href="/admin/level-sessions") Active Instances
|
||||
li
|
||||
a(href="/admin/trial-requests") Trial Requests
|
||||
li
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
|
||||
h1 Latest Games
|
||||
|
||||
each session in view.sessions.models
|
||||
- var url = '/play/level/'+session.get('levelID')+'?session='+session.id
|
||||
.session_tile
|
||||
a(href=url)
|
||||
if session.get('screenshot')
|
||||
img(src=session.get('screenshot'))
|
||||
else
|
||||
img(src="/images/generic-icon.png")
|
||||
.session_info
|
||||
.level_name= session.get('levelName')
|
||||
.creator_name= session.get('creatorName')
|
|
@ -132,8 +132,9 @@ block outer_content
|
|||
|
||||
div.tab-pane#editor-level-tasks-tab-view
|
||||
|
||||
div.tab-pane#editor-level-patches
|
||||
.patches-view
|
||||
div.tab-pane#editor-level-patches.nano
|
||||
.nano-content
|
||||
.patches-view
|
||||
|
||||
div.tab-pane#related-achievements-view
|
||||
|
||||
|
|
|
@ -83,21 +83,21 @@ module.exports = class AnalyticsView extends RootView
|
|||
campaignDauTotal += count
|
||||
else if event.indexOf('DAU classroom') >= 0
|
||||
classroomDauTotal += count
|
||||
eventMap[event] = true;
|
||||
eventMap[event] = true
|
||||
entry.events['DAU campaign total'] = campaignDauTotal
|
||||
eventMap['DAU campaign total'] = true;
|
||||
eventMap['DAU campaign total'] = true
|
||||
campaignDauTotals.unshift(campaignDauTotal)
|
||||
campaignDauTotals.pop() while campaignDauTotals.length > 30
|
||||
if campaignDauTotals.length is 30
|
||||
entry.events['DAU campaign 30-day average'] = Math.round(_.reduce(campaignDauTotals, (a, b) -> a + b) / 30)
|
||||
eventMap['DAU campaign 30-day average'] = true;
|
||||
eventMap['DAU campaign 30-day average'] = true
|
||||
entry.events['DAU classroom total'] = classroomDauTotal
|
||||
eventMap['DAU classroom total'] = true;
|
||||
eventMap['DAU classroom total'] = true
|
||||
classroomDauTotals.unshift(classroomDauTotal)
|
||||
classroomDauTotals.pop() while classroomDauTotals.length > 30
|
||||
if classroomDauTotals.length is 30
|
||||
entry.events['DAU classroom 30-day average'] = Math.round(_.reduce(classroomDauTotals, (a, b) -> a + b) / 30)
|
||||
eventMap['DAU classroom 30-day average'] = true;
|
||||
eventMap['DAU classroom 30-day average'] = true
|
||||
|
||||
@activeUsers.sort (a, b) -> b.day.localeCompare(a.day)
|
||||
@activeUserEventNames = Object.keys(eventMap)
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/admin/level_sessions'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
|
||||
class LevelSessionCollection extends CocoCollection
|
||||
url: '/db/level.session/x/active?project=screenshot,levelName,creatorName'
|
||||
model: LevelSession
|
||||
|
||||
module.exports = class LevelSessionsView extends RootView
|
||||
id: 'admin-level-sessions-view'
|
||||
template: template
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@getLevelSessions()
|
||||
|
||||
getLevelSessions: ->
|
||||
@sessions = @supermodel.loadCollection(new LevelSessionCollection(), 'sessions', {cache: false}).model
|
|
@ -23,6 +23,7 @@ module.exports = class MainAdminView extends RootView
|
|||
'submit #user-search-form': 'onSubmitUserSearchForm'
|
||||
'click #stop-spying-btn': 'onClickStopSpyingButton'
|
||||
'click #increment-button': 'incrementUserAttribute'
|
||||
'click .user-spy-button': 'onClickUserSpyButton'
|
||||
'click #user-search-result': 'onClickUserSearchResult'
|
||||
'click #create-free-sub-btn': 'onClickFreeSubLink'
|
||||
'click #terminal-create': 'onClickTerminalSubLink'
|
||||
|
@ -59,6 +60,18 @@ module.exports = class MainAdminView extends RootView
|
|||
errors.showNotyNetworkError(arguments...)
|
||||
})
|
||||
|
||||
onClickUserSpyButton: (e) ->
|
||||
e.stopPropagation()
|
||||
userID = $(e.target).closest('tr').data('user-id')
|
||||
button = $(e.currentTarget)
|
||||
forms.disableSubmit(button)
|
||||
me.spy(userID, {
|
||||
success: -> window.location.reload()
|
||||
error: ->
|
||||
forms.enableSubmit(button)
|
||||
errors.showNotyNetworkError(arguments...)
|
||||
})
|
||||
|
||||
onSubmitUserSearchForm: (e) ->
|
||||
e.preventDefault()
|
||||
searchValue = @$el.find('#user-search').val()
|
||||
|
@ -76,7 +89,7 @@ module.exports = class MainAdminView extends RootView
|
|||
forms.enableSubmit(@$('#user-search-button'))
|
||||
result = ''
|
||||
if users.length
|
||||
result = ("<tr data-user-id='#{user._id}'><td><code>#{user._id}</code></td><td>#{_.escape(user.name or 'Anonymous')}</td><td>#{_.escape(user.email)}</td></tr>" for user in users)
|
||||
result = ("<tr data-user-id='#{user._id}'><td><code>#{user._id}</code></td><td>#{_.escape(user.name or 'Anonymous')}</td><td>#{_.escape(user.email)}</td><td><button class='user-spy-button'>Spy</button></td></tr>" for user in users)
|
||||
result = "<table class=\"table\">#{result.join('\n')}</table>"
|
||||
@$el.find('#user-search-result').html(result)
|
||||
|
||||
|
|
|
@ -595,7 +595,7 @@ module.exports = class ThangsTabView extends CocoView
|
|||
@level.set 'thangs', thangs
|
||||
return if @editThangView
|
||||
return if skipSerialization
|
||||
serializedLevel = @level.serialize @supermodel, null, null, true
|
||||
serializedLevel = @level.serialize {@supermodel, session: null, otherSession: null, headless: false, sessionless: true, cached: true}
|
||||
try
|
||||
@world.loadFromLevel serializedLevel, false
|
||||
catch error
|
||||
|
|
|
@ -25,9 +25,9 @@ module.exports = class VerifierTest extends CocoClass
|
|||
@loadStartTime = new Date()
|
||||
@god = new God maxAngels: 1, headless: true
|
||||
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, headless: true, fakeSessionConfig: {codeLanguage: @language, callback: @configureSession}
|
||||
@listenToOnce @levelLoader, 'world-necessities-loaded', @onWorldNecessitiesLoaded
|
||||
@listenToOnce @levelLoader, 'world-necessities-loaded', -> _.defer @onWorldNecessitiesLoaded
|
||||
|
||||
onWorldNecessitiesLoaded: ->
|
||||
onWorldNecessitiesLoaded: =>
|
||||
# Called when we have enough to build the world, but not everything is loaded
|
||||
@grabLevelLoaderData()
|
||||
|
||||
|
@ -62,7 +62,7 @@ module.exports = class VerifierTest extends CocoClass
|
|||
@solution = @levelLoader.session.solution
|
||||
|
||||
setupGod: ->
|
||||
@god.setLevel @level.serialize @supermodel, @session
|
||||
@god.setLevel @level.serialize {@supermodel, @session, otherSession: null, headless: true, sessionless: false}
|
||||
@god.setLevelSessionIDs [@session.id]
|
||||
@god.setWorldClassMap @world.classMap
|
||||
@god.lastFlagHistory = @session.get('state').flagHistory
|
||||
|
@ -134,8 +134,10 @@ module.exports = class VerifierTest extends CocoClass
|
|||
setTimeout @cleanup, 100
|
||||
|
||||
cleanup: =>
|
||||
if @levelLoader
|
||||
@stopListening @levelLoader
|
||||
@levelLoader.destroy()
|
||||
if @god
|
||||
@stopListening @god
|
||||
@god.destroy()
|
||||
|
||||
@world = null
|
||||
|
|
|
@ -42,6 +42,7 @@ module.exports = class LadderView extends RootView
|
|||
initialize: (options, @levelID, @leagueType, @leagueID) ->
|
||||
@level = @supermodel.loadModel(new Level(_id: @levelID)).model
|
||||
@level.once 'sync', =>
|
||||
return if @destroyed
|
||||
@levelDescription = marked(@level.get('description')) if @level.get('description')
|
||||
@teams = teamDataFromLevel @level
|
||||
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@levelID), 'your_sessions', {cache: false}).model
|
||||
|
|
|
@ -69,7 +69,7 @@ module.exports = class SpectateLevelView extends RootView
|
|||
@load()
|
||||
|
||||
setLevel: (@level, @supermodel) ->
|
||||
serializedLevel = @level.serialize @supermodel, @session, @otherSession
|
||||
serializedLevel = @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false}
|
||||
@god?.setLevel serializedLevel
|
||||
if @world
|
||||
@world.loadFromLevel serializedLevel, false
|
||||
|
@ -106,7 +106,7 @@ module.exports = class SpectateLevelView extends RootView
|
|||
#at this point, all requisite data is loaded, and sessions are not denormalized
|
||||
team = @world.teamForPlayer(0)
|
||||
@loadOpponentTeam(team)
|
||||
@god.setLevel @level.serialize @supermodel, @session, @otherSession
|
||||
@god.setLevel @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false}
|
||||
@god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id]
|
||||
@god.setWorldClassMap @world.classMap
|
||||
@setTeam team
|
||||
|
|
|
@ -69,7 +69,6 @@ module.exports = class PlayLevelView extends RootView
|
|||
'god:infinite-loop': 'onInfiniteLoop'
|
||||
'level:reload-from-data': 'onLevelReloadFromData'
|
||||
'level:reload-thang-type': 'onLevelReloadThangType'
|
||||
'level:session-will-save': 'onSessionWillSave'
|
||||
'level:started': 'onLevelStarted'
|
||||
'level:loading-view-unveiling': 'onLoadingViewUnveiling'
|
||||
'level:loading-view-unveiled': 'onLoadingViewUnveiled'
|
||||
|
@ -112,7 +111,6 @@ module.exports = class PlayLevelView extends RootView
|
|||
@gameUIState = new GameUIState()
|
||||
|
||||
$(window).on 'resize', @onWindowResize
|
||||
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
||||
|
||||
application.tracker?.enableInspectletJS(@levelID)
|
||||
|
||||
|
@ -130,7 +128,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
@supermodel.collections = givenSupermodel.collections
|
||||
@supermodel.shouldSaveBackups = givenSupermodel.shouldSaveBackups
|
||||
|
||||
serializedLevel = @level.serialize @supermodel, @session, @otherSession
|
||||
serializedLevel = @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false}
|
||||
@god?.setLevel serializedLevel
|
||||
if @world
|
||||
@world.loadFromLevel serializedLevel, false
|
||||
|
@ -246,7 +244,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
@session.set 'multiplayer', false
|
||||
|
||||
setupGod: ->
|
||||
@god.setLevel @level.serialize @supermodel, @session, @otherSession
|
||||
@god.setLevel @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false}
|
||||
@god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id]
|
||||
@god.setWorldClassMap @world.classMap
|
||||
|
||||
|
@ -594,15 +592,6 @@ module.exports = class PlayLevelView extends RootView
|
|||
@bus.removeFirebaseData =>
|
||||
@bus.disconnect()
|
||||
|
||||
onSessionWillSave: (e) ->
|
||||
# Something interesting has happened, so (at a lower frequency), we'll save a screenshot.
|
||||
#@saveScreenshot e.session
|
||||
|
||||
# Throttled
|
||||
saveScreenshot: (session) =>
|
||||
return unless screenshot = @surface?.screenshot()
|
||||
session.save {screenshot: screenshot}, {patch: true, type: 'PUT'}
|
||||
|
||||
onContactClicked: (e) ->
|
||||
Backbone.Mediator.publish 'level:contact-button-pressed', {}
|
||||
@openModalView contactModal = new ContactModal levelID: @level.get('slug') or @level.id, courseID: @courseID, courseInstanceID: @courseInstanceID
|
||||
|
@ -954,20 +943,6 @@ module.exports = class PlayLevelView extends RootView
|
|||
console.error 'Failed to read sessionState in onRealTimeMultiplayerCast'
|
||||
|
||||
console.info 'Submitting my code'
|
||||
# Transpiling code copied from scripts/transpile.coffee
|
||||
# TODO: Should this live somewhere else?
|
||||
transpiledCode = {}
|
||||
for thang, spells of @session.get('code')
|
||||
transpiledCode[thang] = {}
|
||||
for spellID, spell of spells
|
||||
spellName = thang + '/' + spellID
|
||||
continue if @session.get('teamSpells') and not (spellName in @session.get('teamSpells')[@session.get('team')])
|
||||
# console.log "PlayLevelView Transpiling spell #{spellName}"
|
||||
aetherOptions = createAetherOptions functionName: spellID, codeLanguage: @session.get('submittedCodeLanguage'), includeFlow: true
|
||||
aether = new Aether aetherOptions
|
||||
transpiledCode[thang][spellID] = aether.transpile spell
|
||||
# console.log "PlayLevelView transpiled code", transpiledCode
|
||||
@session.set 'transpiledCode', transpiledCode
|
||||
permissions = @session.get 'permissions' ? []
|
||||
unless _.find(permissions, (p) -> p.target is 'public' and p.access is 'read')
|
||||
permissions.push target:'public', access:'read'
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
template = require 'templates/play/level/modal/course-victory-modal'
|
||||
Achievements = require 'collections/Achievements'
|
||||
Level = require 'models/Level'
|
||||
Course = require 'models/Course'
|
||||
ThangType = require 'models/ThangType'
|
||||
ThangTypes = require 'collections/ThangTypes'
|
||||
LevelSessions = require 'collections/LevelSessions'
|
||||
EarnedAchievement = require 'models/EarnedAchievement'
|
||||
LocalMongo = require 'lib/LocalMongo'
|
||||
ProgressView = require './ProgressView'
|
||||
NewItemView = require './NewItemView'
|
||||
Classroom = require 'models/Classroom'
|
||||
utils = require 'core/utils'
|
||||
|
||||
|
@ -18,7 +12,6 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
template: template
|
||||
closesOnClickOutside: false
|
||||
|
||||
|
||||
initialize: (options) ->
|
||||
@courseID = options.courseID
|
||||
@courseInstanceID = options.courseInstanceID
|
||||
|
@ -26,20 +19,10 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
|
||||
@session = options.session
|
||||
@level = options.level
|
||||
@newItems = new ThangTypes()
|
||||
@newHeroes = new ThangTypes()
|
||||
|
||||
|
||||
if @courseInstanceID
|
||||
@classroom = new Classroom()
|
||||
@supermodel.trackRequest(@classroom.fetchForCourseInstance(@courseInstanceID))
|
||||
@achievements = options.achievements
|
||||
if not @achievements
|
||||
@achievements = new Achievements()
|
||||
@achievements.fetchRelatedToLevel(@session.get('level').original)
|
||||
@achievements = @supermodel.loadCollection(@achievements, 'achievements').model
|
||||
@listenToOnce @achievements, 'sync', @onAchievementsLoaded
|
||||
else
|
||||
@onAchievementsLoaded()
|
||||
|
||||
@playSound 'victory'
|
||||
@nextLevel = new Level()
|
||||
|
@ -68,69 +51,12 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
return
|
||||
super(arguments...)
|
||||
|
||||
|
||||
onAchievementsLoaded: ->
|
||||
@achievements.models = _.filter @achievements.models, (m) -> not m.get('query')?.ladderAchievementDifficulty # Don't show higher AI difficulty achievements
|
||||
itemOriginals = []
|
||||
heroOriginals = []
|
||||
achievementIDs = []
|
||||
for achievement in @achievements.models
|
||||
rewards = achievement.get('rewards') or {}
|
||||
heroOriginals.push rewards.heroes or []
|
||||
itemOriginals.push rewards.items or []
|
||||
achievement.completed = LocalMongo.matchesQuery(@session.attributes, achievement.get('query'))
|
||||
achievementIDs.push(achievement.id) if achievement.completed
|
||||
|
||||
itemOriginals = _.uniq _.flatten itemOriginals
|
||||
heroOriginals = _.uniq _.flatten heroOriginals
|
||||
#project = ['original', 'rasterIcon', 'name', 'soundTriggers', 'i18n'] # This is what we need, but the PlayHeroesModal needs more, and so we load more to fill up the supermodel.
|
||||
project = ['original', 'rasterIcon', 'name', 'slug', 'soundTriggers', 'featureImages', 'gems', 'heroClass', 'description', 'components', 'extendedName', 'unlockLevelName', 'i18n']
|
||||
for [newThangTypeCollection, originals] in [[@newItems, itemOriginals], [@newHeroes, heroOriginals]]
|
||||
for original in originals
|
||||
thang= new ThangType()
|
||||
thang.url = "/db/thang.type/#{original}/version"
|
||||
thang.project = project
|
||||
@supermodel.loadModel(thang)
|
||||
newThangTypeCollection.add(thang)
|
||||
|
||||
@newEarnedAchievements = []
|
||||
for achievement in @achievements.models
|
||||
continue unless achievement.completed
|
||||
ea = new EarnedAchievement({
|
||||
collection: achievement.get('collection')
|
||||
triggeredBy: @session.id
|
||||
achievement: achievement.id
|
||||
})
|
||||
if me.isSessionless()
|
||||
@newEarnedAchievements.push ea
|
||||
else
|
||||
ea.save()
|
||||
# Can't just add models to supermodel because each ea has the same url
|
||||
ea.sr = @supermodel.addSomethingResource(ea.cid)
|
||||
@newEarnedAchievements.push ea
|
||||
@listenToOnce ea, 'sync', (model) ->
|
||||
model.sr.markLoaded()
|
||||
if _.all((ea.id for ea in @newEarnedAchievements))
|
||||
unless me.loading
|
||||
@supermodel.loadModel(me, {cache: false})
|
||||
@newEarnedAchievementsResource.markLoaded()
|
||||
|
||||
unless me.isSessionless()
|
||||
# have to use a something resource because addModelResource doesn't handle models being upserted/fetched via POST like we're doing here
|
||||
@newEarnedAchievementsResource = @supermodel.addSomethingResource('earned achievements') if @newEarnedAchievements.length
|
||||
|
||||
|
||||
onLoaded: ->
|
||||
super()
|
||||
@views = []
|
||||
|
||||
# TODO: Add main victory view
|
||||
# TODO: Add level up view
|
||||
# TODO: Add new hero view?
|
||||
|
||||
for newItem in @newItems.models
|
||||
@views.push(new NewItemView({item: newItem}))
|
||||
|
||||
@levelSessions?.remove(@session)
|
||||
@levelSessions?.add(@session)
|
||||
progressView = new ProgressView({
|
||||
level: @level
|
||||
nextLevel: @nextLevel
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Follow up on Close.io leads
|
||||
|
||||
'use strict';
|
||||
if (process.argv.length !== 7) {
|
||||
if (process.argv.length !== 8) {
|
||||
log("Usage: node <script> <Close.io general API key> <Close.io mail API key1> <Close.io mail API key2> <Close.io mail API key3> <mongo connection Url>");
|
||||
process.exit();
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ const demoRequestEmailTemplatesAuto2 = ['tmpl_HJ5zebh1SqC1QydDto05VPUMu4F7i5M35L
|
|||
|
||||
const scriptStartTime = new Date();
|
||||
const closeIoApiKey = process.argv[2];
|
||||
const closeIoMailApiKeys = [process.argv[3], process.argv[4], process.argv[5]]; // Automatic mails sent as API owners
|
||||
const mongoConnUrl = process.argv[6];
|
||||
const closeIoMailApiKeys = [process.argv[3], process.argv[4], process.argv[5], process.argv[6]]; // Automatic mails sent as API owners
|
||||
const mongoConnUrl = process.argv[7];
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
const async = require('async');
|
||||
const request = require('request');
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Upsert new lead data into Close.io
|
||||
|
||||
'use strict';
|
||||
if (process.argv.length !== 9) {
|
||||
log("Usage: node <script> <Close.io general API key> <Close.io mail API key1> <Close.io mail API key2> <Close.io mail API key3> <Close.io EU mail API key> <Intercom 'App ID:API key'> <mongo connection Url>");
|
||||
if (process.argv.length !== 10) {
|
||||
log("Usage: node <script> <Close.io general API key> <Close.io mail API key1> <Close.io mail API key2> <Close.io mail API key3> <Close.io mail API key4> <Close.io EU mail API key> <Intercom 'App ID:API key'> <mongo connection Url>");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
|
@ -64,18 +64,22 @@ const closeIoMailApiKeys = [
|
|||
},
|
||||
{
|
||||
apiKey: process.argv[4],
|
||||
weight: .25
|
||||
weight: .20
|
||||
},
|
||||
{
|
||||
apiKey: process.argv[5],
|
||||
weight: .05
|
||||
},
|
||||
{
|
||||
apiKey: process.argv[6],
|
||||
weight: .05
|
||||
},
|
||||
];
|
||||
const closeIoEuMailApiKey = process.argv[6];
|
||||
const intercomAppIdApiKey = process.argv[7];
|
||||
const closeIoEuMailApiKey = process.argv[7];
|
||||
const intercomAppIdApiKey = process.argv[8];
|
||||
const intercomAppId = intercomAppIdApiKey.split(':')[0];
|
||||
const intercomApiKey = intercomAppIdApiKey.split(':')[1];
|
||||
const mongoConnUrl = process.argv[8];
|
||||
const mongoConnUrl = process.argv[9];
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
const async = require('async');
|
||||
const countryData = require('country-data');
|
||||
|
|
|
@ -142,20 +142,20 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
CourseInstance.findById courseInstanceID, (err, courseInstance) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless courseInstance
|
||||
Course.findById courseInstance.get('courseID'), (err, course) =>
|
||||
Classroom.findById courseInstance.get('classroomID'), (err, classroom) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless course
|
||||
Campaign.findById course.get('campaignID'), (err, campaign) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless campaign
|
||||
levelIDs = (levelID for levelID, level of campaign.get('levels') when not _.contains(level.type, 'ladder'))
|
||||
query = {$and: [{creator: req.user.id}, {'level.original': {$in: levelIDs}}]}
|
||||
cursor = LevelSession.find(query)
|
||||
cursor = cursor.select(req.query.project) if req.query.project
|
||||
cursor.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err?
|
||||
cleandocs = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, cleandocs)
|
||||
return @sendNotFoundError(res) unless classroom
|
||||
levelIDs = []
|
||||
for course in classroom.get('courses') when course._id.equals(courseInstance.get('courseID'))
|
||||
for level in course.levels when not _.contains(level.type, 'ladder')
|
||||
levelIDs.push(level.original + "")
|
||||
query = {$and: [{creator: req.user.id}, {'level.original': {$in: levelIDs}}]}
|
||||
cursor = LevelSession.find(query)
|
||||
cursor = cursor.select(req.query.project) if req.query.project
|
||||
cursor.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err?
|
||||
cleandocs = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, cleandocs)
|
||||
|
||||
getMembersAPI: (req, res, courseInstanceID) ->
|
||||
return @sendUnauthorizedError(res) if not req.user?
|
||||
|
|
|
@ -195,7 +195,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
majorVersion: level.version.major
|
||||
creator: req.user._id+''
|
||||
|
||||
query = Session.find(sessionQuery).select('-screenshot -transpiledCode')
|
||||
query = Session.find(sessionQuery)
|
||||
# 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 ranking readiness
|
||||
query.exec (err, results) =>
|
||||
if err then @sendDatabaseError(res, err) else @sendSuccess res, results
|
||||
|
|
|
@ -31,6 +31,6 @@ AnalyticsLogEventSchema.statics.logEvent = (user, event, properties={}) ->
|
|||
unless config.proxy
|
||||
analyticsMongoose = mongoose.createConnection()
|
||||
analyticsMongoose.open "mongodb://#{config.mongo.analytics_host}:#{config.mongo.analytics_port}/#{config.mongo.analytics_db}", (error) ->
|
||||
log.warn "Couldnt connect to analytics", error
|
||||
log.error "Couldnt connect to analytics", error if error
|
||||
|
||||
module.exports = AnalyticsLogEvent = analyticsMongoose.model('analytics.log.event', AnalyticsLogEventSchema, config.mongo.analytics_collection)
|
||||
|
|
|
@ -84,9 +84,9 @@ LevelSessionSchema.pre 'save', (next) ->
|
|||
|
||||
LevelSessionSchema.statics.privateProperties = ['code', 'submittedCode', 'unsubscribed']
|
||||
LevelSessionSchema.statics.editableProperties = ['multiplayer', 'players', 'code', 'codeLanguage', 'completed', 'state',
|
||||
'levelName', 'creatorName', 'levelID', 'screenshot',
|
||||
'levelName', 'creatorName', 'levelID',
|
||||
'chat', 'teamSpells', 'submitted', 'submittedCodeLanguage',
|
||||
'unsubscribed', 'playtime', 'heroConfig', 'team', 'transpiledCode',
|
||||
'unsubscribed', 'playtime', 'heroConfig', 'team',
|
||||
'browser']
|
||||
LevelSessionSchema.statics.jsonSchema = jsonschema
|
||||
|
||||
|
@ -94,7 +94,7 @@ LevelSessionSchema.set('toObject', {
|
|||
transform: (doc, ret, options) ->
|
||||
req = options.req
|
||||
return ret unless req # TODO: Make deleting properties the default, but the consequences are far reaching
|
||||
|
||||
|
||||
submittedCode = doc.get('submittedCode')
|
||||
unless req.user?.isAdmin() or req.user?.id is doc.get('creator') or ('employer' in (req.user?.get('permissions') ? [])) or not doc.get('submittedCode') # TODO: only allow leaderboard access to non-top-5 solutions
|
||||
ret = _.omit ret, LevelSession.privateProperties
|
||||
|
@ -106,4 +106,32 @@ LevelSessionSchema.set('toObject', {
|
|||
return ret
|
||||
})
|
||||
|
||||
module.exports = LevelSession = mongoose.model('level.session', LevelSessionSchema, 'level.sessions')
|
||||
if config.mongo.level_session_replica_string?
|
||||
levelSessionMongo = mongoose.createConnection()
|
||||
levelSessionMongo.open config.mongo.level_session_replica_string, (error) ->
|
||||
if error
|
||||
log.error "Couldnt connect to session mongo!", error
|
||||
else
|
||||
log.info "Connected to seperate level session server with string", config.mongo.level_session_replica_string
|
||||
else
|
||||
levelSessionMongo = mongoose
|
||||
|
||||
LevelSession = levelSessionMongo.model('level.session', LevelSessionSchema, 'level.sessions')
|
||||
|
||||
if config.mongo.level_session_aux_replica_string?
|
||||
auxLevelSessionMongo = mongoose.createConnection()
|
||||
auxLevelSessionMongo.open config.mongo.level_session_aux_replica_string, (error) ->
|
||||
if error
|
||||
log.error "Couldnt connect to AUX session mongo!", error
|
||||
else
|
||||
log.info "Connected to seperate level AUX session server with string", config.mongo.level_session_aux_replica_string
|
||||
|
||||
auxLevelSession = auxLevelSessionMongo.model('level.session', LevelSessionSchema, 'level.sessions')
|
||||
|
||||
LevelSessionSchema.post 'save', (d) ->
|
||||
return unless d instanceof LevelSession
|
||||
o = d.toObject {transform: ((x, r) -> r), virtuals: false}
|
||||
auxLevelSession.collection.save o, {w:1}, (err, v) ->
|
||||
log.error err.stack if err
|
||||
|
||||
module.exports = LevelSession
|
||||
|
|
|
@ -26,6 +26,11 @@ config.mongo =
|
|||
mongoose_tokyo_replica_string: process.env.COCO_MONGO_MONGOOSE_TOKYO_REPLICA_STRING or ''
|
||||
mongoose_saoPaulo_replica_string : process.env.COCO_MONGO_MONGOOSE_SAOPAULO_REPLICA_STRING or ''
|
||||
|
||||
if process.env.COCO_MONGO_LS_REPLICA_STRING?
|
||||
config.mongo.level_session_replica_string = process.env.COCO_MONGO_LS_REPLICA_STRING
|
||||
|
||||
if process.env.COCO_MONGO_LS_AUX_REPLICA_STRING?
|
||||
config.mongo.level_session_aux_replica_string = process.env.COCO_MONGO_LS_AUX_REPLICA_STRING
|
||||
|
||||
|
||||
if config.tokyo or config.saoPaulo
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
Course = require 'models/Course'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
Achievements = require 'collections/Achievements'
|
||||
CourseVictoryModal = require 'views/play/level/modal/CourseVictoryModal'
|
||||
NewItemView = require 'views/play/level/modal/NewItemView'
|
||||
ProgressView = require 'views/play/level/modal/ProgressView'
|
||||
factories = require 'test/app/factories'
|
||||
|
||||
|
@ -12,7 +10,7 @@ describe 'CourseVictoryModal', ->
|
|||
me.clear()
|
||||
|
||||
it 'will eventually be the only victory modal'
|
||||
|
||||
|
||||
makeViewOptions = ->
|
||||
level = factories.makeLevel()
|
||||
course = factories.makeCourse()
|
||||
|
@ -21,24 +19,18 @@ describe 'CourseVictoryModal', ->
|
|||
course: factories.makeCourse()
|
||||
level: level
|
||||
session: factories.makeLevelSession({ state: { complete: true } }, { level })
|
||||
achievements: new Achievements([factories.makeLevelCompleteAchievement({}, {level: level})])
|
||||
nextLevel: factories.makeLevel()
|
||||
courseInstanceID: courseInstance.id
|
||||
courseID: course.id
|
||||
}
|
||||
|
||||
nextLevelRequest = null
|
||||
|
||||
|
||||
handleRequests = (modal) ->
|
||||
requests = jasmine.Ajax.requests.all()
|
||||
thangRequest = _.find(requests, (r) -> _.string.startsWith(r.url, '/db/thang.type'))
|
||||
thangRequest?.respondWith({status: 200, responseText: factories.makeThangType().stringify()})
|
||||
modal.newEarnedAchievements[0].fakeRequests[0].respondWith({
|
||||
status: 200, responseText: factories.makeEarnedAchievement().stringify()
|
||||
})
|
||||
modal.levelSessions.fakeRequests[0].respondWith({ status: 200, responseText: '[]' })
|
||||
modal.classroom.fakeRequests[0].respondWith({
|
||||
status: 200, responseText: factories.makeClassroom().stringify()
|
||||
status: 200, responseText: factories.makeClassroom().stringify()
|
||||
})
|
||||
if me.fakeRequests
|
||||
lastRequest = _.last(me.fakeRequests)
|
||||
|
@ -47,7 +39,7 @@ describe 'CourseVictoryModal', ->
|
|||
status: 200, responseText: factories.makeUser().stringify()
|
||||
})
|
||||
nextLevelRequest = modal.nextLevel.fakeRequests[0]
|
||||
|
||||
|
||||
describe 'given a course level with a next level and no item or hero rewards', ->
|
||||
modal = null
|
||||
|
||||
|
@ -63,7 +55,7 @@ describe 'CourseVictoryModal', ->
|
|||
expect(modal.views[0] instanceof ProgressView).toBe(true)
|
||||
|
||||
it '(demo)', -> jasmine.demoModal(modal)
|
||||
|
||||
|
||||
describe 'its ProgressView', ->
|
||||
it 'has a next level button which navigates to the next level on click', ->
|
||||
spyOn(application.router, 'navigate')
|
||||
|
@ -71,12 +63,12 @@ describe 'CourseVictoryModal', ->
|
|||
expect(button.length).toBe(1)
|
||||
button.click()
|
||||
expect(application.router.navigate).toHaveBeenCalled()
|
||||
|
||||
|
||||
it 'has two columns', ->
|
||||
expect(modal.$('.row:first .col-sm-12').length).toBe(0)
|
||||
expect(modal.$('.row:first .col-sm-5').length).toBe(1)
|
||||
expect(modal.$('.row:first .col-sm-7').length).toBe(1)
|
||||
|
||||
|
||||
describe 'given a course level without a next level', ->
|
||||
modal = null
|
||||
|
||||
|
@ -91,13 +83,13 @@ describe 'CourseVictoryModal', ->
|
|||
handleRequests(modal)
|
||||
nextLevelRequest.respondWith({status: 404, responseText: '{}'})
|
||||
_.defer done
|
||||
|
||||
|
||||
describe 'its ProgressView', ->
|
||||
it 'has a single large column, since there is no next level to display', ->
|
||||
expect(modal.$('.row:first .col-sm-12').length).toBe(1)
|
||||
expect(modal.$('.row:first .col-sm-5').length).toBe(0)
|
||||
expect(modal.$('.row:first .col-sm-7').length).toBe(0)
|
||||
|
||||
|
||||
it 'has a done button which navigates to the CourseDetailsView for the given course instance', ->
|
||||
spyOn(application.router, 'navigate')
|
||||
button = modal.$el.find('#done-btn')
|
||||
|
@ -106,32 +98,3 @@ describe 'CourseVictoryModal', ->
|
|||
expect(application.router.navigate).toHaveBeenCalled()
|
||||
|
||||
it '(demo)', -> jasmine.demoModal(modal)
|
||||
|
||||
|
||||
describe 'given a course level with a new item', ->
|
||||
modal = null
|
||||
|
||||
beforeEach (done) ->
|
||||
options = makeViewOptions()
|
||||
|
||||
# insert new item into achievement properties
|
||||
achievement = options.achievements.first()
|
||||
rewards = _.cloneDeep(achievement.get('rewards'))
|
||||
rewards.items = ["53e4108204c00d4607a89f78"]
|
||||
achievement.set('rewards', rewards)
|
||||
|
||||
modal = new CourseVictoryModal(options)
|
||||
handleRequests(modal)
|
||||
nextLevelRequest.respondWith({status: 200, responseText: factories.makeLevel().stringify()})
|
||||
_.defer done
|
||||
|
||||
it 'includes a NewItemView when the level rewards a new item', ->
|
||||
expect(_.size(modal.views)).toBe(2)
|
||||
expect(modal.views[0] instanceof NewItemView).toBe(true)
|
||||
|
||||
it 'continues to the ProgressView when you click the continue button', ->
|
||||
expect(modal.currentView instanceof NewItemView).toBe(true)
|
||||
modal.$el.find('#continue-btn').click()
|
||||
expect(modal.currentView instanceof ProgressView).toBe(true)
|
||||
|
||||
it '(demo)', -> jasmine.demoModal(modal)
|
||||
|
|
Loading…
Reference in a new issue