mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 15:48:11 -05:00
Lock course content to classrooms
This commit is contained in:
parent
c9ed76471a
commit
675e3290ac
44 changed files with 857 additions and 513 deletions
|
@ -10,3 +10,9 @@ module.exports = class CourseInstances extends CocoCollection
|
||||||
options.data ?= {}
|
options.data ?= {}
|
||||||
options.data.ownerID = ownerID
|
options.data.ownerID = ownerID
|
||||||
@fetch(options)
|
@fetch(options)
|
||||||
|
|
||||||
|
fetchForClassroom: (classroomID, options={}) ->
|
||||||
|
classroomID = classroomID.id or classroomID # handle if they pass in a user
|
||||||
|
options.data ?= {}
|
||||||
|
options.data.classroomID = classroomID
|
||||||
|
@fetch(options)
|
|
@ -5,6 +5,12 @@ module.exports = class LevelSessionCollection extends CocoCollection
|
||||||
url: '/db/level.session'
|
url: '/db/level.session'
|
||||||
model: LevelSession
|
model: LevelSession
|
||||||
|
|
||||||
|
fetchMineForCourseInstance: (courseInstanceID, options) ->
|
||||||
|
options = _.extend({
|
||||||
|
url: "/db/course_instance/#{courseInstanceID}/my-course-level-sessions"
|
||||||
|
}, options)
|
||||||
|
@fetch(options)
|
||||||
|
|
||||||
fetchForCourseInstance: (courseInstanceID, options) ->
|
fetchForCourseInstance: (courseInstanceID, options) ->
|
||||||
options = _.extend({
|
options = _.extend({
|
||||||
url: "/db/course_instance/#{courseInstanceID}/my-course-level-sessions"
|
url: "/db/course_instance/#{courseInstanceID}/my-course-level-sessions"
|
||||||
|
|
|
@ -4,3 +4,12 @@ Level = require 'models/Level'
|
||||||
module.exports = class LevelCollection extends CocoCollection
|
module.exports = class LevelCollection extends CocoCollection
|
||||||
url: '/db/level'
|
url: '/db/level'
|
||||||
model: Level
|
model: Level
|
||||||
|
|
||||||
|
fetchForClassroom: (classroomID, options={}) ->
|
||||||
|
options.url = "/db/classroom/#{classroomID}/levels"
|
||||||
|
@fetch(options)
|
||||||
|
|
||||||
|
fetchForClassroomAndCourse: (classroomID, courseID, options={}) ->
|
||||||
|
options.url = "/db/classroom/#{classroomID}/courses/#{courseID}/levels"
|
||||||
|
@fetch(options)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
go = (path, options) -> -> @routeDirectly path, arguments, options
|
go = (path, options) -> -> @routeDirectly path, arguments, options
|
||||||
redirect = (path) -> -> @navigate(path, { trigger: true, replace: true })
|
redirect = (path) -> -> @navigate(path, { trigger: true, replace: true })
|
||||||
|
utils = require './utils'
|
||||||
|
|
||||||
module.exports = class CocoRouter extends Backbone.Router
|
module.exports = class CocoRouter extends Backbone.Router
|
||||||
|
|
||||||
|
@ -13,6 +14,8 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'': ->
|
'': ->
|
||||||
if window.serverConfig.picoCTF
|
if window.serverConfig.picoCTF
|
||||||
return @routeDirectly 'play/CampaignView', ['picoctf'], {}
|
return @routeDirectly 'play/CampaignView', ['picoctf'], {}
|
||||||
|
if utils.getQueryVariable 'hour_of_code'
|
||||||
|
return @navigate "/play", {trigger: true, replace: true}
|
||||||
return @routeDirectly('NewHomeView', [])
|
return @routeDirectly('NewHomeView', [])
|
||||||
|
|
||||||
'about': go('AboutView')
|
'about': go('AboutView')
|
||||||
|
|
|
@ -178,4 +178,5 @@ prunePath = (delta, path) ->
|
||||||
|
|
||||||
module.exports.DOC_SKIP_PATHS = [
|
module.exports.DOC_SKIP_PATHS = [
|
||||||
'_id','version', 'commitMessage', 'parent', 'created',
|
'_id','version', 'commitMessage', 'parent', 'created',
|
||||||
'slug', 'index', '__v', 'patches', 'creator', 'js', 'watchers']
|
'slug', 'index', '__v', 'patches', 'creator', 'js', 'watchers', 'levelsUpdated'
|
||||||
|
]
|
|
@ -1,8 +1,10 @@
|
||||||
|
Levels = require 'collections/Levels'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
# Result: Each course instance gains a property, numCompleted, that is the
|
# Result: Each course instance gains a property, numCompleted, that is the
|
||||||
# number of students in that course instance who have completed ALL of
|
# number of students in that course instance who have completed ALL of
|
||||||
# the levels in thate course
|
# the levels in thate course
|
||||||
calculateDots: (classrooms, courses, courseInstances, campaigns) ->
|
calculateDots: (classrooms, courses, courseInstances) ->
|
||||||
for classroom in classrooms.models
|
for classroom in classrooms.models
|
||||||
# map [user, level] => session so we don't have to do find TODO
|
# map [user, level] => session so we don't have to do find TODO
|
||||||
for course, courseIndex in courses.models
|
for course, courseIndex in courses.models
|
||||||
|
@ -10,9 +12,9 @@ module.exports =
|
||||||
continue if not instance
|
continue if not instance
|
||||||
instance.numCompleted = 0
|
instance.numCompleted = 0
|
||||||
instance.numStarted = 0
|
instance.numStarted = 0
|
||||||
campaign = campaigns.get(course.get('campaignID'))
|
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true})
|
||||||
for userID in instance.get('members')
|
for userID in instance.get('members')
|
||||||
levelCompletes = _.map campaign.getNonLadderLevels().models, (level) ->
|
levelCompletes = _.map levels.models, (level) ->
|
||||||
return true if level.isLadder()
|
return true if level.isLadder()
|
||||||
#TODO: Hella slow! Do the mapping first!
|
#TODO: Hella slow! Do the mapping first!
|
||||||
session = _.find classroom.sessions.models, (session) ->
|
session = _.find classroom.sessions.models, (session) ->
|
||||||
|
@ -24,13 +26,13 @@ module.exports =
|
||||||
if _.any levelCompletes
|
if _.any levelCompletes
|
||||||
instance.numStarted += 1
|
instance.numStarted += 1
|
||||||
|
|
||||||
calculateEarliestIncomplete: (classroom, courses, campaigns, courseInstances, students) ->
|
calculateEarliestIncomplete: (classroom, courses, courseInstances, students) ->
|
||||||
# Loop through all the combinations of things, return the first one that somebody hasn't finished
|
# Loop through all the combinations of things, return the first one that somebody hasn't finished
|
||||||
for course, courseIndex in courses.models
|
for course, courseIndex in courses.models
|
||||||
instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
||||||
continue if not instance
|
continue if not instance
|
||||||
campaign = campaigns.get(course.get('campaignID'))
|
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true})
|
||||||
for level, levelIndex in campaign.getNonLadderLevels().models
|
for level, levelIndex in levels.models
|
||||||
userIDs = []
|
userIDs = []
|
||||||
for user in students.models
|
for user in students.models
|
||||||
userID = user.id
|
userID = user.id
|
||||||
|
@ -49,15 +51,15 @@ module.exports =
|
||||||
}
|
}
|
||||||
null
|
null
|
||||||
|
|
||||||
calculateLatestComplete: (classroom, courses, campaigns, courseInstances, students) ->
|
calculateLatestComplete: (classroom, courses, courseInstances, students) ->
|
||||||
# Loop through all the combinations of things in reverse order, return the level that anyone's finished
|
# Loop through all the combinations of things in reverse order, return the level that anyone's finished
|
||||||
courseModels = courses.models.slice()
|
courseModels = courses.models.slice()
|
||||||
for course, courseIndex in courseModels.reverse() #
|
for course, courseIndex in courseModels.reverse() #
|
||||||
courseIndex = courses.models.length - courseIndex - 1 #compensate for reverse
|
courseIndex = courses.models.length - courseIndex - 1 #compensate for reverse
|
||||||
instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
||||||
continue if not instance
|
continue if not instance
|
||||||
campaign = campaigns.get(course.get('campaignID'))
|
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true})
|
||||||
levelModels = campaign.getNonLadderLevels().models.slice()
|
levelModels = levels.models.slice()
|
||||||
for level, levelIndex in levelModels.reverse() #
|
for level, levelIndex in levelModels.reverse() #
|
||||||
levelIndex = levelModels.length - levelIndex - 1 #compensate for reverse
|
levelIndex = levelModels.length - levelIndex - 1 #compensate for reverse
|
||||||
userIDs = []
|
userIDs = []
|
||||||
|
@ -86,9 +88,9 @@ module.exports =
|
||||||
conceptData[classroom.id] = {}
|
conceptData[classroom.id] = {}
|
||||||
|
|
||||||
for course, courseIndex in courses.models
|
for course, courseIndex in courses.models
|
||||||
campaign = campaigns.get(course.get('campaignID'))
|
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true})
|
||||||
|
|
||||||
for level in campaign.getNonLadderLevels().models
|
for level in levels.models
|
||||||
levelID = level.get('original')
|
levelID = level.get('original')
|
||||||
|
|
||||||
for concept in level.get('concepts')
|
for concept in level.get('concepts')
|
||||||
|
@ -111,7 +113,7 @@ module.exports =
|
||||||
conceptData[classroom.id][concept].completed = false
|
conceptData[classroom.id][concept].completed = false
|
||||||
conceptData
|
conceptData
|
||||||
|
|
||||||
calculateAllProgress: (classrooms, courses, campaigns, courseInstances, students) ->
|
calculateAllProgress: (classrooms, courses, courseInstances, students) ->
|
||||||
# Loop through all combinations and record:
|
# Loop through all combinations and record:
|
||||||
# Completeness for each student/course
|
# Completeness for each student/course
|
||||||
# Completeness for each student/level
|
# Completeness for each student/level
|
||||||
|
@ -133,9 +135,9 @@ module.exports =
|
||||||
progressData[classroom.id][course.id] = { completed: false, started: false }
|
progressData[classroom.id][course.id] = { completed: false, started: false }
|
||||||
continue
|
continue
|
||||||
progressData[classroom.id][course.id] = { completed: true, started: false } # to be updated
|
progressData[classroom.id][course.id] = { completed: true, started: false } # to be updated
|
||||||
|
|
||||||
campaign = campaigns.get(course.get('campaignID'))
|
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true})
|
||||||
for level in campaign.getNonLadderLevels().models
|
for level in levels.models
|
||||||
levelID = level.get('original')
|
levelID = level.get('original')
|
||||||
progressData[classroom.id][course.id][levelID] = { completed: students.size() > 0, started: false }
|
progressData[classroom.id][course.id][levelID] = { completed: students.size() > 0, started: false }
|
||||||
|
|
||||||
|
|
|
@ -1254,6 +1254,7 @@
|
||||||
concepts_covered: "Concepts covered"
|
concepts_covered: "Concepts covered"
|
||||||
print_guide: "Print Guide (PDF)"
|
print_guide: "Print Guide (PDF)"
|
||||||
view_guide_online: "View Guide Online (PDF)"
|
view_guide_online: "View Guide Online (PDF)"
|
||||||
|
last_updated: "Last updated:"
|
||||||
grants_lifetime_access: "Grants lifetime access to all Courses." # New enrollment modal
|
grants_lifetime_access: "Grants lifetime access to all Courses." # New enrollment modal
|
||||||
enrollment_credits_available: "Enrollment Credits Available:"
|
enrollment_credits_available: "Enrollment Credits Available:"
|
||||||
description: "Description" # ClassroomSettingsModal
|
description: "Description" # ClassroomSettingsModal
|
||||||
|
|
|
@ -11,31 +11,6 @@ module.exports = class Campaign extends CocoModel
|
||||||
saveBackups: true
|
saveBackups: true
|
||||||
@denormalizedLevelProperties: _.keys(_.omit(schema.properties.levels.additionalProperties.properties, ['unlocks', 'position', 'rewards']))
|
@denormalizedLevelProperties: _.keys(_.omit(schema.properties.levels.additionalProperties.properties, ['unlocks', 'position', 'rewards']))
|
||||||
@denormalizedCampaignProperties: ['name', 'i18n', 'slug']
|
@denormalizedCampaignProperties: ['name', 'i18n', 'slug']
|
||||||
|
|
||||||
statsForSessions: (sessions) ->
|
|
||||||
return null unless sessions
|
|
||||||
stats = {}
|
|
||||||
sessions = sessions.models or sessions
|
|
||||||
sessions = _.sortBy sessions, (s) -> s.get('changed')
|
|
||||||
levels = _.values(@get('levels'))
|
|
||||||
levels = (level for level in levels when not _.contains(level.type, 'ladder'))
|
|
||||||
levelOriginals = _.pluck(levels, 'original')
|
|
||||||
sessionOriginals = (session.get('level').original for session in sessions when session.get('state').complete)
|
|
||||||
levelsLeft = _.size(_.difference(levelOriginals, sessionOriginals))
|
|
||||||
lastSession = _.last(sessions)
|
|
||||||
stats.levels = {
|
|
||||||
size: _.size(levels)
|
|
||||||
left: levelsLeft
|
|
||||||
done: levelsLeft is 0
|
|
||||||
numDone: _.size(levels) - levelsLeft
|
|
||||||
pctDone: (100 * (_.size(levels) - levelsLeft) / _.size(levels)).toFixed(1) + '%'
|
|
||||||
lastPlayed: if lastSession then _.findWhere levels, { original: lastSession.get('level').original } else null
|
|
||||||
first: _.first(levels)
|
|
||||||
arena: _.find _.values(@get('levels')), (level) -> _.contains(level.type, 'ladder')
|
|
||||||
}
|
|
||||||
sum = (nums) -> _.reduce(nums, (s, num) -> s + num) or 0
|
|
||||||
stats.playtime = sum((session.get('playtime') or 0 for session in sessions))
|
|
||||||
return stats
|
|
||||||
|
|
||||||
getLevels: ->
|
getLevels: ->
|
||||||
levels = new Levels(_.values(@get('levels')))
|
levels = new Levels(_.values(@get('levels')))
|
||||||
|
|
|
@ -32,3 +32,59 @@ module.exports = class Classroom extends CocoModel
|
||||||
}
|
}
|
||||||
_.extend options, opts
|
_.extend options, opts
|
||||||
@fetch(options)
|
@fetch(options)
|
||||||
|
|
||||||
|
getLevels: (options={}) ->
|
||||||
|
# options: courseID, withoutLadderLevels
|
||||||
|
Levels = require 'collections/Levels'
|
||||||
|
courses = @get('courses')
|
||||||
|
return new Levels() unless courses
|
||||||
|
levelObjects = []
|
||||||
|
for course in courses
|
||||||
|
if options.courseID and options.courseID isnt course._id
|
||||||
|
continue
|
||||||
|
levelObjects.push(course.levels)
|
||||||
|
levels = new Levels(_.flatten(levelObjects))
|
||||||
|
if options.withoutLadderLevels
|
||||||
|
levels.remove(levels.filter((level) -> level.isLadder()))
|
||||||
|
return levels
|
||||||
|
|
||||||
|
getLadderLevel: (courseID) ->
|
||||||
|
Levels = require 'collections/Levels'
|
||||||
|
courses = @get('courses')
|
||||||
|
course = _.findWhere(courses, {_id: courseID})
|
||||||
|
return unless course
|
||||||
|
levels = new Levels(course.levels)
|
||||||
|
return levels.find (l) -> l.isLadder()
|
||||||
|
|
||||||
|
statsForSessions: (sessions, courseID) ->
|
||||||
|
return null unless sessions
|
||||||
|
stats = {}
|
||||||
|
sessions = sessions.models or sessions
|
||||||
|
sessions = _.sortBy sessions, (s) -> s.get('changed')
|
||||||
|
arena = @getLadderLevel(courseID)
|
||||||
|
levels = @getLevels({courseID: courseID, withoutLadderLevels: true})
|
||||||
|
levelOriginals = levels.pluck('original')
|
||||||
|
sessionOriginals = (session.get('level').original for session in sessions when session.get('state').complete)
|
||||||
|
levelsLeft = _.size(_.difference(levelOriginals, sessionOriginals))
|
||||||
|
lastSession = _.last(sessions)
|
||||||
|
stats.levels = {
|
||||||
|
size: levels.size()
|
||||||
|
left: levelsLeft
|
||||||
|
done: levelsLeft is 0
|
||||||
|
numDone: levels.size() - levelsLeft
|
||||||
|
pctDone: (100 * (levels.size() - levelsLeft) / levels.size()).toFixed(1) + '%'
|
||||||
|
lastPlayed: if lastSession then levels.findWhere({ original: lastSession.get('level').original }) else null
|
||||||
|
first: levels.first()
|
||||||
|
arena: arena
|
||||||
|
}
|
||||||
|
sum = (nums) -> _.reduce(nums, (s, num) -> s + num) or 0
|
||||||
|
stats.playtime = sum((session.get('playtime') or 0 for session in sessions))
|
||||||
|
return stats
|
||||||
|
|
||||||
|
fetchForCourseInstance: (courseInstanceID, options={}) ->
|
||||||
|
CourseInstance = require 'models/CourseInstance'
|
||||||
|
courseInstance = if _.isString(courseInstanceID) then new CourseInstance({_id:courseInstanceID}) else courseInstanceID
|
||||||
|
options = _.extend(options, {
|
||||||
|
url: _.result(courseInstance, 'url') + '/classroom'
|
||||||
|
})
|
||||||
|
@fetch(options)
|
|
@ -251,3 +251,7 @@ module.exports = class Level extends CocoModel
|
||||||
|
|
||||||
isLadder: ->
|
isLadder: ->
|
||||||
return @get('type')?.indexOf('ladder') > -1
|
return @get('type')?.indexOf('ladder') > -1
|
||||||
|
|
||||||
|
fetchNextForCourse: (levelOriginalID, courseInstanceID, options={}) ->
|
||||||
|
options.url = "/db/course_instance/#{courseInstanceID}/levels/#{levelOriginalID}/next"
|
||||||
|
@fetch(options)
|
|
@ -96,8 +96,9 @@ module.exports = class SuperModel extends Backbone.Model
|
||||||
jqxhr.done -> res.markLoaded()
|
jqxhr.done -> res.markLoaded()
|
||||||
jqxhr.fail -> res.markFailed()
|
jqxhr.fail -> res.markFailed()
|
||||||
@storeResource(res, value)
|
@storeResource(res, value)
|
||||||
|
return jqxhr
|
||||||
|
|
||||||
trackRequests: (jqxhrs, value=1) -> @trackRequest(jqxhr) for jqxhr in jqxhrs
|
trackRequests: (jqxhrs, value=1) -> @trackRequest(jqxhr, value) for jqxhr in jqxhrs
|
||||||
|
|
||||||
# replace or overwrite
|
# replace or overwrite
|
||||||
shouldSaveBackups: (model) -> false
|
shouldSaveBackups: (model) -> false
|
||||||
|
|
|
@ -45,6 +45,7 @@ _.extend CampaignSchema.properties, {
|
||||||
showIfUnlocked: { type: 'string', links: [{rel: 'db', href: '/db/level/{($)}/version'}], format: 'latest-version-original-reference' }
|
showIfUnlocked: { type: 'string', links: [{rel: 'db', href: '/db/level/{($)}/version'}], format: 'latest-version-original-reference' }
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
levelsUpdated: c.date()
|
||||||
|
|
||||||
levels: { type: 'object', format: 'levels', additionalProperties: {
|
levels: { type: 'object', format: 'levels', additionalProperties: {
|
||||||
title: 'Level'
|
title: 'Level'
|
||||||
|
|
|
@ -20,6 +20,15 @@ _.extend ClassroomSchema.properties,
|
||||||
type: 'boolean'
|
type: 'boolean'
|
||||||
default: false
|
default: false
|
||||||
description: 'Visual only; determines if the classroom is in the "archived" list of the normal list.'
|
description: 'Visual only; determines if the classroom is in the "archived" list of the normal list.'
|
||||||
|
courses: c.array { title: 'Courses' }, c.object { title: 'Course' }, {
|
||||||
|
_id: c.objectId()
|
||||||
|
levels: c.array { title: 'Levels' }, c.object { title: 'Level' }, {
|
||||||
|
type: c.shortString()
|
||||||
|
original: c.objectId()
|
||||||
|
name: {type: 'string'}
|
||||||
|
slug: {type: 'string'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.extendBasicProperties ClassroomSchema, 'Classroom'
|
c.extendBasicProperties ClassroomSchema, 'Classroom'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- var completed = session && session.get('state') && session.get('state').complete;
|
- var completed = session && session.get('state') && session.get('state').complete;
|
||||||
h3 #{i}. #{level.name.replace('Course: ', '')}
|
h3 #{i}. #{level.get('name').replace('Course: ', '')}
|
||||||
if session
|
if session
|
||||||
p
|
p
|
||||||
span.spr(data-i18n="courses.play_time")
|
span.spr(data-i18n="courses.play_time")
|
||||||
|
|
|
@ -89,7 +89,6 @@ block content
|
||||||
if !(inCourse || view.teacherMode)
|
if !(inCourse || view.teacherMode)
|
||||||
- continue;
|
- continue;
|
||||||
- var course = view.courses.get(courseInstance.get('courseID'));
|
- var course = view.courses.get(courseInstance.get('courseID'));
|
||||||
- var campaign = view.campaigns.get(course.get('campaignID'));
|
|
||||||
- var sessions = courseInstance.sessionsByUser[user.id] || [];
|
- var sessions = courseInstance.sessionsByUser[user.id] || [];
|
||||||
if !(course.get('free') || paidFor)
|
if !(course.get('free') || paidFor)
|
||||||
- continue;
|
- continue;
|
||||||
|
@ -98,8 +97,8 @@ block content
|
||||||
.col-sm-3.text-right= course.get('name')
|
.col-sm-3.text-right= course.get('name')
|
||||||
.col-sm-9
|
.col-sm-9
|
||||||
if inCourse
|
if inCourse
|
||||||
- var levels = campaign.get('levels');
|
- var levels = view.classroom.getLevels({courseID: course.id});
|
||||||
- var numLevels = Object.keys(levels).length;
|
- var numLevels = levels.size();
|
||||||
- var sessionMap = _.zipObject(_.map(sessions, function(s) { return s.get('level').original; }), sessions);
|
- var sessionMap = _.zipObject(_.map(sessions, function(s) { return s.get('level').original; }), sessions);
|
||||||
- var levelCellWidth = 100.00;
|
- var levelCellWidth = 100.00;
|
||||||
if numLevels > 0
|
if numLevels > 0
|
||||||
|
@ -107,9 +106,10 @@ block content
|
||||||
- var css = "width:"+levelCellWidth+"%;"
|
- var css = "width:"+levelCellWidth+"%;"
|
||||||
- var i = 0;
|
- var i = 0;
|
||||||
.progress
|
.progress
|
||||||
each level, levelID in campaign.get('levels')
|
each trimModel in levels.models
|
||||||
|
- var level = view.levels.get(trimModel.get('original')); // get the level loaded through the db
|
||||||
- i++
|
- i++
|
||||||
- var session = sessionMap[levelID];
|
- var session = sessionMap[level.get('original')];
|
||||||
a(href=view.getLevelURL(level, course, courseInstance, session))
|
a(href=view.getLevelURL(level, course, courseInstance, session))
|
||||||
- var content = view.levelPopoverContent(level, session, i);
|
- var content = view.levelPopoverContent(level, session, i);
|
||||||
if session && session.get('state') && session.get('state').complete
|
if session && session.get('state') && session.get('state').complete
|
||||||
|
|
|
@ -16,160 +16,105 @@ block content
|
||||||
br
|
br
|
||||||
br
|
br
|
||||||
|
|
||||||
if (noCourseInstance || noCourseInstanceSelected) && course
|
p
|
||||||
h1= course.get('name')
|
// TODO: format this text all good and stuff
|
||||||
if noCourseInstance
|
strong
|
||||||
p(data-i18n="courses.not_enrolled")
|
if view.courseInstance.get('name')
|
||||||
p
|
span= view.courseInstance.get('name')
|
||||||
span.spr(data-i18n="courses.visit_pref")
|
else if view.classroom.get('name')
|
||||||
a(href="/courses", data-i18n="courses.courses")
|
span= view.classroom.get('name')
|
||||||
span.spl(data-i18n="courses.visit_suf")
|
else
|
||||||
else if noCourseInstanceSelected
|
span(data-i18n='courses.unnamed_class')
|
||||||
p(data-i18n="courses.select_class")
|
|
||||||
.container-fluid
|
|
||||||
.row
|
|
||||||
.col-md-6
|
|
||||||
select.form-control.select-instance
|
|
||||||
each courseInstance in courseInstances
|
|
||||||
if courseInstance.get('name')
|
|
||||||
option(value="#{courseInstance.id}")= courseInstance.get('name')
|
|
||||||
else
|
|
||||||
option(value="#{courseInstance.id}", data-i18n="courses.unnamed")
|
|
||||||
.col-md-6
|
|
||||||
button.btn.btn-success.btn-select-instance(data-i18n="courses.select")
|
|
||||||
else if !course || !courseInstance
|
|
||||||
h1(data-i18n="common.loading")
|
|
||||||
else
|
|
||||||
p
|
|
||||||
// TODO: format this text all good and stuff
|
|
||||||
strong
|
|
||||||
if courseInstance.get('name')
|
|
||||||
span= courseInstance.get('name')
|
|
||||||
else if view.classroom.get('name')
|
|
||||||
span= view.classroom.get('name')
|
|
||||||
else
|
|
||||||
span(data-i18n='courses.unnamed_class')
|
|
||||||
|
|
||||||
if !view.owner.isNew() && view.getOwnerName() && courseInstance.get('name') != 'Single Player'
|
if !view.owner.isNew() && view.getOwnerName() && view.courseInstance.get('name') != 'Single Player'
|
||||||
span.spl -
|
span.spl -
|
||||||
span.spl(data-i18n='courses.teacher')
|
span.spl(data-i18n='courses.teacher')
|
||||||
span.spr :
|
span.spr :
|
||||||
//a(href="/user/#{view.owner.id}") // Don't link to profiles until we improve them
|
//a(href="/user/#{view.owner.id}") // Don't link to profiles until we improve them
|
||||||
span
|
span
|
||||||
strong= view.getOwnerName()
|
strong= view.getOwnerName()
|
||||||
|
|
||||||
h1
|
h1
|
||||||
| #{course.get('name')}
|
| #{view.course.get('name')}
|
||||||
if view.courseComplete
|
if view.courseComplete
|
||||||
span.spl -
|
span.spl -
|
||||||
span.spl(data-i18n='courses.complete')
|
span.spl(data-i18n='courses.complete')
|
||||||
span !
|
span !
|
||||||
|
|
||||||
p
|
p
|
||||||
if courseInstance.get('description')
|
if view.courseInstance.get('description')
|
||||||
each line in courseInstance.get('description').split('\n')
|
each line in view.courseInstance.get('description').split('\n')
|
||||||
div= line
|
div= line
|
||||||
|
|
||||||
if view.courseComplete && !view.teacherMode
|
if view.courseComplete && !view.teacherMode
|
||||||
.jumbotron
|
.jumbotron
|
||||||
if promptForSchool
|
.row
|
||||||
.row
|
.col-md-6
|
||||||
.col-md-6.col-md-offset-3
|
if view.arenaLevel
|
||||||
form.form#school-form
|
a.btn.btn-lg.btn-success.btn-play-level(data-level-slug=view.arenaLevel.get('slug'), data-level-id=view.arenaLevel.get('original'))
|
||||||
.form-group
|
h1
|
||||||
label.control-label(for="course-complete-school-input")
|
span(data-i18n='courses.arena')
|
||||||
span.spr(data-i18n="signup.school_name")
|
span.spr :
|
||||||
em.optional-note
|
span= view.arenaLevel.get('name')
|
||||||
| (
|
p= view.arenaLevel.get('description').replace(/!\[.*?\)/, '')
|
||||||
span(data-i18n="signup.optional")
|
else
|
||||||
| ):
|
a.btn.btn-lg.btn-success.disabled
|
||||||
.input-border
|
h1(data-i18n='courses.arena_soon_title')
|
||||||
input#course-complete-school-input.input-large.form-control(name="schoolName", data-i18n="[placeholder]signup.school_name_placeholder")
|
p
|
||||||
button.btn.btn-primary.btn-submit.no-school(type="submit", data-i18n='courses.none')
|
span.spr(data-i18n='courses.arena_soon_description')
|
||||||
button.btn.btn-info.btn-submit.save-school(type="submit", data-i18n='courses.save')
|
span= view.course.get('name')
|
||||||
.row
|
span .
|
||||||
if view.singlePlayerMode && !me.isAnonymous()
|
.col-md-6
|
||||||
.col-md-6.col-md-offset-3
|
if view.nextCourseInstance
|
||||||
a.btn.btn-lg.btn-success(href="/play")
|
a.btn.btn-lg.btn-success(href="/courses/#{view.nextCourse.id}/#{view.nextCourseInstance.id}")
|
||||||
h1(data-i18n='courses.play_campaign_title')
|
h1= view.nextCourse.get('name')
|
||||||
p(data-i18n='courses.play_campaign_description')
|
p= view.nextCourse.get('description')
|
||||||
else if view.singlePlayerMode && me.isAnonymous()
|
else if view.nextCourse
|
||||||
.col-md-6
|
a.btn.btn-lg.btn-success.disabled
|
||||||
a.btn.btn-lg.btn-success.signup-button
|
h1= view.nextCourse.get('name')
|
||||||
h1(data-i18n='courses.create_account_title')
|
p.text-uppercase
|
||||||
p(data-i18n='courses.create_account_description')
|
em(data-i18n='courses.not_enrolled1')
|
||||||
.col-md-6
|
p(data-i18n='courses.not_enrolled2')
|
||||||
a.btn.btn-lg.btn-success(href="/play")
|
else
|
||||||
h1(data-i18n='courses.preview_campaign_title')
|
a.btn.btn-lg.btn-success(disabled=!view.nextCourse ? "disabled" : "")
|
||||||
p(data-i18n='courses.preview_campaign_description')
|
h1(data-i18n='courses.next_course')
|
||||||
else if !view.singlePlayerMode
|
p.text-uppercase
|
||||||
.col-md-6
|
em(data-i18n='courses.coming_soon1')
|
||||||
if view.arenaLevel
|
p(data-i18n='courses.coming_soon2')
|
||||||
a.btn.btn-lg.btn-success.btn-play-level(data-level-slug=view.arenaLevel.slug, data-level-id=view.arenaLevel.original)
|
|
||||||
h1
|
|
||||||
span(data-i18n='courses.arena')
|
|
||||||
span.spr :
|
|
||||||
span= view.arenaLevel.name
|
|
||||||
p= view.arenaLevel.description.replace(/!\[.*?\)/, '')
|
|
||||||
else
|
|
||||||
a.btn.btn-lg.btn-success.disabled
|
|
||||||
h1(data-i18n='courses.arena_soon_title')
|
|
||||||
p
|
|
||||||
span.spr(data-i18n='courses.arena_soon_description')
|
|
||||||
span= course.get('name')
|
|
||||||
span .
|
|
||||||
.col-md-6
|
|
||||||
if view.nextCourseInstance
|
|
||||||
a.btn.btn-lg.btn-success(href="/courses/#{view.nextCourse.id}/#{view.nextCourseInstance.id}")
|
|
||||||
h1= view.nextCourse.get('name')
|
|
||||||
p= view.nextCourse.get('description')
|
|
||||||
else if view.nextCourse
|
|
||||||
a.btn.btn-lg.btn-success.disabled
|
|
||||||
h1= view.nextCourse.get('name')
|
|
||||||
p.text-uppercase
|
|
||||||
em(data-i18n='courses.not_enrolled1')
|
|
||||||
p(data-i18n='courses.not_enrolled2')
|
|
||||||
else
|
|
||||||
a.btn.btn-lg.btn-success(disabled=!view.nextCourse ? "disabled" : "")
|
|
||||||
h1(data-i18n='courses.next_course')
|
|
||||||
p.text-uppercase
|
|
||||||
em(data-i18n='courses.coming_soon1')
|
|
||||||
p(data-i18n='courses.coming_soon2')
|
|
||||||
|
|
||||||
.available-courses-title(data-i18n='courses.available_levels')
|
.available-courses-title(data-i18n='courses.available_levels')
|
||||||
table.table.table-striped.table-condensed
|
table.table.table-striped.table-condensed
|
||||||
thead
|
thead
|
||||||
|
tr
|
||||||
|
th
|
||||||
|
th(data-i18n="clans.status")
|
||||||
|
th(data-i18n="resources.level")
|
||||||
|
th(data-i18n="courses.concepts")
|
||||||
|
tbody
|
||||||
|
- var previousLevelCompleted = true;
|
||||||
|
- var lastLevelCompleted = view.getLastLevelCompleted();
|
||||||
|
- var passedLastCompletedLevel = !lastLevelCompleted;
|
||||||
|
- var levelCount = 0;
|
||||||
|
each level in view.levels.models
|
||||||
|
- var levelStatus = null;
|
||||||
|
if view.userLevelStateMap[me.id]
|
||||||
|
- levelStatus = view.userLevelStateMap[me.id][level.get('original')]
|
||||||
tr
|
tr
|
||||||
th
|
td
|
||||||
th(data-i18n="clans.status")
|
if previousLevelCompleted || view.teacherMode || !passedLastCompletedLevel || levelStatus
|
||||||
th(data-i18n="resources.level")
|
- var i18n = level.get('type') === 'course-ladder' ? 'play.compete' : 'home.play';
|
||||||
th(data-i18n="courses.concepts")
|
button.btn.btn-success.btn-play-level(data-level-slug=level.get('slug'), data-i18n=i18n, data-level-id=level.get('original'))
|
||||||
tbody
|
td
|
||||||
if campaign
|
if view.userLevelStateMap[me.id]
|
||||||
- var previousLevelCompleted = true;
|
div= view.userLevelStateMap[me.id][level.get('original')]
|
||||||
- var lastLevelCompleted = view.getLastLevelCompleted();
|
- previousLevelCompleted = view.userLevelStateMap[me.id][level.get('original')] === 'complete'
|
||||||
- var passedLastCompletedLevel = false;
|
else
|
||||||
- var levelCount = 0;
|
- previousLevelCompleted = false
|
||||||
each level, levelID in campaign.get('levels')
|
td= ++levelCount + '. ' + level.get('name').replace('Course: ', '')
|
||||||
- var levelStatus = null;
|
td
|
||||||
if userLevelStateMap[me.id]
|
if view.levelConceptMap[level.get('original')]
|
||||||
- levelStatus = userLevelStateMap[me.id][levelID]
|
each concept in view.course.get('concepts')
|
||||||
tr
|
if view.levelConceptMap[level.get('original')][concept]
|
||||||
td
|
span.spr.concept(data-i18n="concepts." + concept)
|
||||||
if previousLevelCompleted || view.teacherMode || !passedLastCompletedLevel || levelStatus
|
if level.get('original') === lastLevelCompleted
|
||||||
- var i18n = level.type === 'course-ladder' ? 'play.compete' : 'home.play';
|
- passedLastCompletedLevel = true
|
||||||
button.btn.btn-success.btn-play-level(data-level-slug=level.slug, data-i18n=i18n, data-level-id=levelID)
|
|
||||||
td
|
|
||||||
if userLevelStateMap[me.id]
|
|
||||||
div= userLevelStateMap[me.id][levelID]
|
|
||||||
- previousLevelCompleted = userLevelStateMap[me.id][levelID] === 'complete'
|
|
||||||
else
|
|
||||||
- previousLevelCompleted = false
|
|
||||||
td= ++levelCount + '. ' + level.name.replace('Course: ', '')
|
|
||||||
td
|
|
||||||
if levelConceptMap[levelID]
|
|
||||||
each concept in course.get('concepts')
|
|
||||||
if levelConceptMap[levelID][concept]
|
|
||||||
span.spr.concept(data-i18n="concepts." + concept)
|
|
||||||
if levelID === lastLevelCompleted
|
|
||||||
- passedLastCompletedLevel = true
|
|
||||||
|
|
|
@ -45,42 +45,10 @@ block content
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
- var showHOCComplete = false;
|
|
||||||
if view.hocCourseInstance
|
|
||||||
- var course = view.courses.get(view.hocCourseInstance.get('courseID'));
|
|
||||||
- var campaign = view.campaigns.get(course.get('campaignID'));
|
|
||||||
- var stats = campaign.statsForSessions(view.hocCourseInstance.sessions);
|
|
||||||
- showHOCComplete = stats.levels.done && !view.classrooms.size();
|
|
||||||
|
|
||||||
.text-center
|
.text-center
|
||||||
if !showHOCComplete
|
h1(data-i18n="courses.welcome_to_page") Welcome to your Courses page!
|
||||||
h1(data-i18n="courses.welcome_to_page") Welcome to your Courses page!
|
|
||||||
else
|
|
||||||
h1(data-i18n="courses.completed_hoc")
|
|
||||||
h2(data-i18n="courses.ready_for_more_header")
|
|
||||||
ul.text-left
|
|
||||||
li(data-i18n="courses.ready_for_more_1")
|
|
||||||
li(data-i18n="courses.ready_for_more_2")
|
|
||||||
li(data-i18n="courses.ready_for_more_3")
|
|
||||||
a.btn.btn-lg.btn-success(href="/play") Play Now
|
|
||||||
|
|
||||||
if view.hocCourseInstance && !view.classrooms.size()
|
if view.classrooms.size()
|
||||||
h3(data-i18n="courses.saved_games")
|
|
||||||
hr
|
|
||||||
|
|
||||||
.course-instance-entry
|
|
||||||
h3
|
|
||||||
span(data-i18n="courses.hoc")
|
|
||||||
span.spr :
|
|
||||||
span.spr(data-i18n="courses.course")
|
|
||||||
span 1
|
|
||||||
span.spr= (me.get('aceConfig') || {}).language === 'javascript' ? 'JavaScript' : 'Python'
|
|
||||||
small
|
|
||||||
a#change-language-link(data-i18n="courses.change_language")
|
|
||||||
+course-instance-body(view.hocCourseInstance)
|
|
||||||
.clearfix
|
|
||||||
|
|
||||||
else if view.classrooms.size()
|
|
||||||
h3.text-uppercase(data-i18n="courses.my_classes")
|
h3.text-uppercase(data-i18n="courses.my_classes")
|
||||||
hr
|
hr
|
||||||
|
|
||||||
|
@ -106,13 +74,9 @@ block content
|
||||||
span.spr= course.get('name')
|
span.spr= course.get('name')
|
||||||
small
|
small
|
||||||
a(href="/courses/"+courseInstance.get('courseID')+'/'+courseInstance.id, data-i18n="courses.view_levels")
|
a(href="/courses/"+courseInstance.get('courseID')+'/'+courseInstance.id, data-i18n="courses.view_levels")
|
||||||
+course-instance-body(courseInstance)
|
+course-instance-body(courseInstance, classroom)
|
||||||
.clearfix
|
.clearfix
|
||||||
|
|
||||||
else
|
|
||||||
.text-center
|
|
||||||
button#start-new-game-btn.btn.btn-success.btn-lg(data-i18n="courses.start_new_game")
|
|
||||||
|
|
||||||
h3.text-uppercase(data-i18n="courses.join_class")
|
h3.text-uppercase(data-i18n="courses.join_class")
|
||||||
hr
|
hr
|
||||||
|
|
||||||
|
@ -131,16 +95,9 @@ block content
|
||||||
.alert.alert-danger= view.errorMessage
|
.alert.alert-danger= view.errorMessage
|
||||||
|
|
||||||
|
|
||||||
#begin-hoc-area.hide
|
mixin course-instance-body(courseInstance, classroom)
|
||||||
h3.text-center(data-i18n="common.loading")
|
|
||||||
.progress.progress-striped.active
|
|
||||||
.progress-bar(style="width: 100%")
|
|
||||||
|
|
||||||
|
|
||||||
mixin course-instance-body(courseInstance)
|
|
||||||
- var course = view.courses.get(courseInstance.get('courseID'));
|
- var course = view.courses.get(courseInstance.get('courseID'));
|
||||||
- var campaign = view.campaigns.get(course.get('campaignID'));
|
- var stats = classroom.statsForSessions(courseInstance.sessions, course.id);
|
||||||
- var stats = campaign.statsForSessions(courseInstance.sessions);
|
|
||||||
if stats.levels.done
|
if stats.levels.done
|
||||||
.text-success
|
.text-success
|
||||||
span.glyphicon.glyphicon-ok
|
span.glyphicon.glyphicon-ok
|
||||||
|
@ -150,19 +107,19 @@ mixin course-instance-body(courseInstance)
|
||||||
if stats.levels.done
|
if stats.levels.done
|
||||||
- var arenaLevel = stats.levels.arena;
|
- var arenaLevel = stats.levels.arena;
|
||||||
if arenaLevel
|
if arenaLevel
|
||||||
- var arenaURL = "/play/ladder/"+arenaLevel.slug+"/course/"+courseInstance.id;
|
- var arenaURL = "/play/ladder/"+arenaLevel.get('slug')+"/course/"+courseInstance.id;
|
||||||
a.btn.btn-warning.btn-lg(href=arenaURL)
|
a.btn.btn-warning.btn-lg(href=arenaURL)
|
||||||
span(data-i18n="courses.play_arena")
|
span(data-i18n="courses.play_arena")
|
||||||
else
|
else
|
||||||
a.btn.btn-default.btn-lg(disabled=true, data-i18n="courses.course_complete")
|
a.btn.btn-default.btn-lg(disabled=true, data-i18n="courses.course_complete")
|
||||||
else if courseInstance.sessions.size()
|
else if courseInstance.sessions.size()
|
||||||
- var lastLevel = stats.levels.lastPlayed;
|
- var lastLevel = stats.levels.lastPlayed;
|
||||||
- var levelURL = "/play/level/"+lastLevel.slug+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id;
|
- var levelURL = "/play/level/"+lastLevel.get('slug')+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id;
|
||||||
a.btn.btn-success.btn-lg(href=levelURL)
|
a.btn.btn-success.btn-lg(href=levelURL)
|
||||||
span(data-i18n="common.continue")
|
span(data-i18n="common.continue")
|
||||||
else
|
else
|
||||||
- var firstLevel = stats.levels.first;
|
- var firstLevel = stats.levels.first;
|
||||||
- var levelURL = "/play/level/"+firstLevel.slug+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id;
|
- var levelURL = "/play/level/"+firstLevel.get('slug')+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id;
|
||||||
a.btn.btn-info.btn-lg(href=levelURL)
|
a.btn.btn-info.btn-lg(href=levelURL)
|
||||||
span(data-i18n="courses.start")
|
span(data-i18n="courses.start")
|
||||||
|
|
||||||
|
|
|
@ -216,7 +216,8 @@ mixin courseProgressTab
|
||||||
span(data-i18n='teacher.select_course')
|
span(data-i18n='teacher.select_course')
|
||||||
span.spr :
|
span.spr :
|
||||||
select.course-select
|
select.course-select
|
||||||
each course in view.courses.models
|
each trimCourse in view.classroom.get('courses')
|
||||||
|
- var course = view.courses.get(trimCourse._id);
|
||||||
option(value=course.id)
|
option(value=course.id)
|
||||||
= course.get('name')
|
= course.get('name')
|
||||||
if view.progressData
|
if view.progressData
|
||||||
|
@ -229,8 +230,7 @@ mixin courseProgressTab
|
||||||
|
|
||||||
mixin courseOverview
|
mixin courseOverview
|
||||||
- var course = view.selectedCourse
|
- var course = view.selectedCourse
|
||||||
- var campaign = view.campaigns.get(course.get('campaignID'))
|
- var levels = view.classroom.getLevels({courseID: course.id, withoutLadderLevels: true}).models
|
||||||
- var levels = campaign.getNonLadderLevels().models
|
|
||||||
.course-overview-row
|
.course-overview-row
|
||||||
.course-title.student-name
|
.course-title.student-name
|
||||||
span= course.get('name')
|
span= course.get('name')
|
||||||
|
@ -248,8 +248,7 @@ mixin studentLevelsRow(student)
|
||||||
div.student-email.small-details= student.get('email')
|
div.student-email.small-details= student.get('email')
|
||||||
div.student-levels-progress
|
div.student-levels-progress
|
||||||
- var course = view.selectedCourse
|
- var course = view.selectedCourse
|
||||||
- var campaign = view.campaigns.get(course.get('campaignID'))
|
- var levels = view.classroom.getLevels({courseID: course.id, withoutLadderLevels: true}).models
|
||||||
- var levels = campaign.getNonLadderLevels().models
|
|
||||||
each level, index in levels
|
each level, index in levels
|
||||||
- var progress = view.progressData.get({ classroom: view.classroom, course: course, level: level, user: student })
|
- var progress = view.progressData.get({ classroom: view.classroom, course: course, level: level, user: student })
|
||||||
+progressDot(progress, index+1)
|
+progressDot(progress, index+1)
|
||||||
|
@ -292,7 +291,8 @@ mixin bulkAssignControls
|
||||||
span(data-i18n='teacher.bulk_assign')
|
span(data-i18n='teacher.bulk_assign')
|
||||||
span :
|
span :
|
||||||
select.bulk-course-select.form-control
|
select.bulk-course-select.form-control
|
||||||
each course in view.courses.models
|
each trimCourse in view.classroom.get('courses')
|
||||||
|
- var course = view.courses.get(trimCourse._id)
|
||||||
option(value=course.id)
|
option(value=course.id)
|
||||||
= course.get('name')
|
= course.get('name')
|
||||||
button.btn.btn-primary-alt.assign-to-selected-students
|
button.btn.btn-primary-alt.assign-to-selected-students
|
||||||
|
|
|
@ -75,7 +75,8 @@ mixin classRow(classroom)
|
||||||
if classroom.get('members').length == 0
|
if classroom.get('members').length == 0
|
||||||
+addStudentsButton(classroom)
|
+addStudentsButton(classroom)
|
||||||
else
|
else
|
||||||
each course, index in view.courses.models
|
each trimCourse, index in classroom.get('courses') || []
|
||||||
|
- var course = view.courses.get(trimCourse._id);
|
||||||
+progressDot(classroom, course, index)
|
+progressDot(classroom, course, index)
|
||||||
.view-class-arrow.col-xs-1
|
.view-class-arrow.col-xs-1
|
||||||
a.view-class-arrow-inner.glyphicon.glyphicon-chevron-right(data-classroom-id=classroom.id, href=('/teachers/classes/' + classroom.id))
|
a.view-class-arrow-inner.glyphicon.glyphicon-chevron-right(data-classroom-id=classroom.id, href=('/teachers/classes/' + classroom.id))
|
||||||
|
|
|
@ -66,6 +66,7 @@ block content
|
||||||
.clearfix
|
.clearfix
|
||||||
|
|
||||||
mixin course-info(course)
|
mixin course-info(course)
|
||||||
|
- var campaign = view.campaigns.get(course.get('campaignID'));
|
||||||
.course-info
|
.course-info
|
||||||
.text-h4.semibold
|
.text-h4.semibold
|
||||||
= course.get('name')
|
= course.get('name')
|
||||||
|
@ -93,3 +94,7 @@ mixin course-info(course)
|
||||||
| (
|
| (
|
||||||
span(data-i18n='teacher.guides_coming_soon')
|
span(data-i18n='teacher.guides_coming_soon')
|
||||||
| )
|
| )
|
||||||
|
if campaign && campaign.get('levelsUpdated')
|
||||||
|
p.small.m-t-2
|
||||||
|
span.spr(data-i18n="courses.last_updated")
|
||||||
|
span= moment(campaign.get('levelsUpdated')).format('LL')
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
.modal-body
|
.modal-body
|
||||||
.container-fluid
|
.container-fluid
|
||||||
.row
|
.row
|
||||||
- var colClass = view.nextLevel ? 'col-sm-7' : 'col-sm-12'
|
- var colClass = !view.nextLevel.isNew() ? 'col-sm-7' : 'col-sm-12'
|
||||||
div(class=colClass)
|
div(class=colClass)
|
||||||
.well.well-sm.well-parchment
|
.well.well-sm.well-parchment
|
||||||
h3.text-uppercase(data-i18n='play_level.completed_level')
|
h3.text-uppercase(data-i18n='play_level.completed_level')
|
||||||
|
@ -25,12 +25,12 @@
|
||||||
.col-sm-8
|
.col-sm-8
|
||||||
h3.text-uppercase.text-center= i18n(view.course.attributes, 'name')
|
h3.text-uppercase.text-center= i18n(view.course.attributes, 'name')
|
||||||
.col-sm-4
|
.col-sm-4
|
||||||
- var stats = view.campaign.statsForSessions(view.levelSessions)
|
- var stats = view.classroom.statsForSessions(view.levelSessions, view.course.id)
|
||||||
h1
|
h1
|
||||||
span #{stats.levels.numDone}/#{stats.levels.size}
|
span #{stats.levels.numDone}/#{stats.levels.size}
|
||||||
|
|
||||||
|
|
||||||
if view.nextLevel
|
if !view.nextLevel.isNew()
|
||||||
.col-sm-5
|
.col-sm-5
|
||||||
.well.well-sm.well-parchment
|
.well.well-sm.well-parchment
|
||||||
h3.text-uppercase
|
h3.text-uppercase
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
// TODO: Add this and rest of campaign functionality
|
// TODO: Add this and rest of campaign functionality
|
||||||
// button#continue-btn.btn.btn-illustrated.btn-default.btn-block.btn-lg.text-uppercase View Leaderboards
|
// button#continue-btn.btn.btn-illustrated.btn-default.btn-block.btn-lg.text-uppercase View Leaderboards
|
||||||
.col-sm-5
|
.col-sm-5
|
||||||
if view.nextLevel
|
if !view.nextLevel.isNew()
|
||||||
button#next-level-btn.btn.btn-illustrated.btn-primary.btn-block.btn-lg.text-uppercase(data-i18n='play_level.next_level')
|
button#next-level-btn.btn.btn-illustrated.btn-primary.btn-block.btn-lg.text-uppercase(data-i18n='play_level.next_level')
|
||||||
else
|
else
|
||||||
button#done-btn.btn.btn-illustrated.btn-primary.btn-block.btn-lg.text-uppercase(data-i18n='play_level.done')
|
button#done-btn.btn.btn-illustrated.btn-primary.btn-block.btn-lg.text-uppercase(data-i18n='play_level.done')
|
||||||
|
|
|
@ -38,9 +38,6 @@ module.exports = class NewHomeView extends RootView
|
||||||
@variation ?= me.getHomepageGroup()
|
@variation ?= me.getHomepageGroup()
|
||||||
|
|
||||||
window.tracker?.trackEvent 'Homepage Loaded', category: 'Homepage'
|
window.tracker?.trackEvent 'Homepage Loaded', category: 'Homepage'
|
||||||
if @getQueryVariable 'hour_of_code'
|
|
||||||
application.router.navigate "/hoc", trigger: true
|
|
||||||
|
|
||||||
if me.isTeacher()
|
if me.isTeacher()
|
||||||
@trialRequests = new TrialRequests()
|
@trialRequests = new TrialRequests()
|
||||||
@trialRequests.fetchOwn()
|
@trialRequests.fetchOwn()
|
||||||
|
|
|
@ -6,6 +6,7 @@ Classroom = require 'models/Classroom'
|
||||||
Classrooms = require 'collections/Classrooms'
|
Classrooms = require 'collections/Classrooms'
|
||||||
LevelSession = require 'models/LevelSession'
|
LevelSession = require 'models/LevelSession'
|
||||||
Prepaids = require 'collections/Prepaids'
|
Prepaids = require 'collections/Prepaids'
|
||||||
|
Levels = require 'collections/Levels'
|
||||||
RootView = require 'views/core/RootView'
|
RootView = require 'views/core/RootView'
|
||||||
template = require 'templates/courses/classroom-view'
|
template = require 'templates/courses/classroom-view'
|
||||||
User = require 'models/User'
|
User = require 'models/User'
|
||||||
|
@ -37,9 +38,7 @@ module.exports = class ClassroomView extends RootView
|
||||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||||
@courses.comparator = '_id'
|
@courses.comparator = '_id'
|
||||||
@supermodel.loadCollection(@courses)
|
@supermodel.loadCollection(@courses)
|
||||||
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign })
|
|
||||||
@courses.comparator = '_id'
|
@courses.comparator = '_id'
|
||||||
@supermodel.loadCollection(@campaigns, { data: { type: 'course' }})
|
|
||||||
@courseInstances = new CocoCollection([], { url: "/db/course_instance", model: CourseInstance})
|
@courseInstances = new CocoCollection([], { url: "/db/course_instance", model: CourseInstance})
|
||||||
@courseInstances.comparator = 'courseID'
|
@courseInstances.comparator = 'courseID'
|
||||||
@supermodel.loadCollection(@courseInstances, { data: { classroomID: classroomID } })
|
@supermodel.loadCollection(@courseInstances, { data: { classroomID: classroomID } })
|
||||||
|
@ -55,6 +54,11 @@ module.exports = class ClassroomView extends RootView
|
||||||
@ownedClassrooms = new Classrooms()
|
@ownedClassrooms = new Classrooms()
|
||||||
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||||
@supermodel.trackCollection(@ownedClassrooms)
|
@supermodel.trackCollection(@ownedClassrooms)
|
||||||
|
@levels = new Levels()
|
||||||
|
@levels.fetchForClassroom(classroomID, {data: {project: 'name,slug,original'}})
|
||||||
|
@levels.on 'add', (model) -> @_byId[model.get('original')] = model # so you can 'get' them
|
||||||
|
|
||||||
|
@supermodel.trackCollection(@levels)
|
||||||
|
|
||||||
onCourseInstancesSync: ->
|
onCourseInstancesSync: ->
|
||||||
@sessions = new CocoCollection([], { model: LevelSession })
|
@sessions = new CocoCollection([], { model: LevelSession })
|
||||||
|
@ -90,9 +94,7 @@ module.exports = class ClassroomView extends RootView
|
||||||
for courseInstance in @courseInstances.models
|
for courseInstance in @courseInstances.models
|
||||||
courseID = courseInstance.get('courseID')
|
courseID = courseInstance.get('courseID')
|
||||||
course = @courses.get(courseID)
|
course = @courses.get(courseID)
|
||||||
campaignID = course.get('campaignID')
|
courseInstance.sessions.course = course
|
||||||
campaign = @campaigns.get(campaignID)
|
|
||||||
courseInstance.sessions.campaign = campaign
|
|
||||||
super()
|
super()
|
||||||
|
|
||||||
afterRender: ->
|
afterRender: ->
|
||||||
|
@ -153,10 +155,10 @@ module.exports = class ClassroomView extends RootView
|
||||||
return '' unless user.sessions?
|
return '' unless user.sessions?
|
||||||
session = user.sessions.last()
|
session = user.sessions.last()
|
||||||
return '' unless session
|
return '' unless session
|
||||||
campaign = session.collection.campaign
|
course = session.collection.course
|
||||||
levelOriginal = session.get('level').original
|
levelOriginal = session.get('level').original
|
||||||
campaignLevel = campaign.get('levels')[levelOriginal]
|
level = @levels.findWhere({original: levelOriginal})
|
||||||
return "#{campaign.get('fullName')}, #{campaignLevel.name}"
|
return "#{course.get('name')}, #{level.get('name')}"
|
||||||
|
|
||||||
userPlaytimeString: (user) ->
|
userPlaytimeString: (user) ->
|
||||||
return '' unless user.sessions?
|
return '' unless user.sessions?
|
||||||
|
@ -240,4 +242,4 @@ module.exports = class ClassroomView extends RootView
|
||||||
|
|
||||||
getLevelURL: (level, course, courseInstance, session) ->
|
getLevelURL: (level, course, courseInstance, session) ->
|
||||||
return null unless @teacherMode and _.all(arguments)
|
return null unless @teacherMode and _.all(arguments)
|
||||||
"/play/level/#{level.slug}?course=#{course.id}&course-instance=#{courseInstance.id}&session=#{session.id}&observing=true"
|
"/play/level/#{level.get('slug')}?course=#{course.id}&course-instance=#{courseInstance.id}&session=#{session.id}&observing=true"
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
Campaign = require 'models/Campaign'
|
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
|
||||||
Course = require 'models/Course'
|
Course = require 'models/Course'
|
||||||
|
Courses = require 'collections/Courses'
|
||||||
|
LevelSessions = require 'collections/LevelSessions'
|
||||||
CourseInstance = require 'models/CourseInstance'
|
CourseInstance = require 'models/CourseInstance'
|
||||||
|
CourseInstances = require 'collections/CourseInstances'
|
||||||
Classroom = require 'models/Classroom'
|
Classroom = require 'models/Classroom'
|
||||||
Classrooms = require 'collections/Classrooms'
|
Classrooms = require 'collections/Classrooms'
|
||||||
LevelSession = require 'models/LevelSession'
|
Levels = require 'collections/Levels'
|
||||||
RootView = require 'views/core/RootView'
|
RootView = require 'views/core/RootView'
|
||||||
template = require 'templates/courses/course-details'
|
template = require 'templates/courses/course-details'
|
||||||
User = require 'models/User'
|
User = require 'models/User'
|
||||||
|
@ -14,7 +15,6 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
id: 'course-details-view'
|
id: 'course-details-view'
|
||||||
template: template
|
template: template
|
||||||
teacherMode: false
|
teacherMode: false
|
||||||
singlePlayerMode: false
|
|
||||||
memberSort: 'nameAsc'
|
memberSort: 'nameAsc'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
|
@ -25,125 +25,64 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
constructor: (options, @courseID, @courseInstanceID) ->
|
constructor: (options, @courseID, @courseInstanceID) ->
|
||||||
super options
|
super options
|
||||||
@ownedClassrooms = new Classrooms()
|
@ownedClassrooms = new Classrooms()
|
||||||
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
@courses = new Courses()
|
||||||
@supermodel.trackCollection(@ownedClassrooms)
|
@course = new Course()
|
||||||
@courseID ?= options.courseID
|
@levelSessions = new LevelSessions()
|
||||||
@courseInstanceID ?= options.courseInstanceID
|
@courseInstance = new CourseInstance({_id: @courseInstanceID})
|
||||||
|
@owner = new User()
|
||||||
@classroom = new Classroom()
|
@classroom = new Classroom()
|
||||||
@course = @supermodel.getModel(Course, @courseID) or new Course _id: @courseID
|
@levels = new Levels()
|
||||||
@listenTo @course, 'sync', @onCourseSync
|
@courseInstances = new CourseInstances()
|
||||||
if @course.loaded
|
|
||||||
@onCourseSync()
|
|
||||||
else
|
|
||||||
@supermodel.loadModel @course
|
|
||||||
|
|
||||||
getRenderData: ->
|
@supermodel.trackRequest @ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||||
context = super()
|
@supermodel.trackRequest(@courses.fetch().then(=>
|
||||||
context.campaign = @campaign
|
@course = @courses.get(@courseID)
|
||||||
context.course = @course if @course?.loaded
|
))
|
||||||
context.courseInstance = @courseInstance if @courseInstance?.loaded
|
sessionsLoaded = @supermodel.trackRequest(@levelSessions.fetchForCourseInstance(@courseInstanceID, {cache: false}))
|
||||||
context.courseInstances = @courseInstances?.models ? []
|
|
||||||
context.levelConceptMap = @levelConceptMap ? {}
|
|
||||||
context.noCourseInstance = @noCourseInstance
|
|
||||||
context.noCourseInstanceSelected = @noCourseInstanceSelected
|
|
||||||
context.userLevelStateMap = @userLevelStateMap ? {}
|
|
||||||
context.promptForSchool = @courseComplete and not me.isAnonymous() and not me.get('schoolName') and not storage.load('no-school')
|
|
||||||
context
|
|
||||||
|
|
||||||
afterRender: ->
|
@supermodel.trackRequest(@courseInstance.fetch().then(=>
|
||||||
super()
|
return if @destroyed
|
||||||
if @supermodel.finished() and @courseComplete and me.isAnonymous() and @options.justBeatLevel
|
@teacherMode = @courseInstance.get('ownerID') is me.id
|
||||||
# TODO: Make an intermediate modal that tells them they've finished HoC and has some snazzy stuff for convincing players to sign up instead of just throwing up the bare CreateAccountModal
|
|
||||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
|
||||||
@openModalView new CreateAccountModal showSignupRationale: true
|
|
||||||
|
|
||||||
onCourseSync: ->
|
@owner = new User({_id: @courseInstance.get('ownerID')})
|
||||||
|
@supermodel.trackRequest(@owner.fetch())
|
||||||
|
|
||||||
|
classroomID = @courseInstance.get('classroomID')
|
||||||
|
@classroom = new Classroom({ _id: classroomID })
|
||||||
|
@supermodel.trackRequest(@classroom.fetch())
|
||||||
|
|
||||||
|
levelsLoaded = @supermodel.trackRequest(@levels.fetchForClassroomAndCourse(classroomID, @courseID, {
|
||||||
|
data: { project: 'concepts,type,slug,name,original,description' }
|
||||||
|
}))
|
||||||
|
|
||||||
|
@supermodel.trackRequest($.when(levelsLoaded, sessionsLoaded).then(=>
|
||||||
|
@buildSessionStats()
|
||||||
|
return if @destroyed
|
||||||
|
if @memberStats[me.id]?.totalLevelsCompleted >= @levels.size() - 1 # Don't need to complete arena
|
||||||
|
# need to figure out the next course instance
|
||||||
|
@courseComplete = true
|
||||||
|
@courseInstances.comparator = 'courseID'
|
||||||
|
@supermodel.trackRequest(@courseInstances.fetchForClassroom(classroomID).then(=>
|
||||||
|
@nextCourseInstance = _.find @courseInstances.models, (ci) => ci.get('courseID') > @courseID
|
||||||
|
if @nextCourseInstance
|
||||||
|
nextCourseID = @nextCourseInstance.get('courseID')
|
||||||
|
@nextCourse = @courses.get(nextCourseID)
|
||||||
|
))
|
||||||
|
@promptForSchool = @courseComplete and not me.isAnonymous() and not me.get('schoolName') and not storage.load('no-school')
|
||||||
|
))
|
||||||
|
))
|
||||||
|
|
||||||
|
buildSessionStats: ->
|
||||||
return if @destroyed
|
return if @destroyed
|
||||||
# console.log 'onCourseSync'
|
|
||||||
if me.isAnonymous() and (not me.get('hourOfCode') and not @course.get('hourOfCode'))
|
|
||||||
@noCourseInstance = true
|
|
||||||
@render()
|
|
||||||
return
|
|
||||||
return if @campaign?
|
|
||||||
campaignID = @course.get('campaignID')
|
|
||||||
@campaign = @supermodel.getModel(Campaign, campaignID) or new Campaign _id: campaignID
|
|
||||||
@listenTo @campaign, 'sync', @onCampaignSync
|
|
||||||
if @campaign.loaded
|
|
||||||
@onCampaignSync()
|
|
||||||
else
|
|
||||||
@supermodel.loadModel @campaign
|
|
||||||
@render()
|
|
||||||
|
|
||||||
onCampaignSync: ->
|
|
||||||
return if @destroyed
|
|
||||||
# console.log 'onCampaignSync'
|
|
||||||
if @courseInstanceID
|
|
||||||
@loadCourseInstance(@courseInstanceID)
|
|
||||||
else unless me.isAnonymous()
|
|
||||||
@loadCourseInstances()
|
|
||||||
@levelConceptMap = {}
|
@levelConceptMap = {}
|
||||||
for levelID, level of @campaign.get('levels')
|
for level in @levels.models
|
||||||
@levelConceptMap[levelID] ?= {}
|
@levelConceptMap[level.get('original')] ?= {}
|
||||||
for concept in level.concepts
|
for concept in level.get('concepts')
|
||||||
@levelConceptMap[levelID][concept] = true
|
@levelConceptMap[level.get('original')][concept] = true
|
||||||
if level.type is 'course-ladder'
|
if level.get('type') is 'course-ladder'
|
||||||
@arenaLevel = level
|
@arenaLevel = level
|
||||||
@render()
|
|
||||||
|
|
||||||
loadCourseInstances: ->
|
|
||||||
@courseInstances = new CocoCollection [], {url: "/db/user/#{me.id}/course_instances", model: CourseInstance, comparator: 'courseID'}
|
|
||||||
@listenToOnce @courseInstances, 'sync', @onCourseInstancesSync
|
|
||||||
@supermodel.loadCollection @courseInstances, 'course_instances'
|
|
||||||
|
|
||||||
loadAllCourses: ->
|
|
||||||
@allCourses = new CocoCollection [], {url: "/db/course", model: Course, comparator: '_id'}
|
|
||||||
@listenToOnce @allCourses, 'sync', @onAllCoursesSync
|
|
||||||
@supermodel.loadCollection @allCourses, 'courses'
|
|
||||||
|
|
||||||
loadCourseInstance: (courseInstanceID) ->
|
|
||||||
return if @destroyed
|
|
||||||
# console.log 'loadCourseInstance'
|
|
||||||
return if @courseInstance?
|
|
||||||
@courseInstanceID = courseInstanceID
|
|
||||||
@courseInstance = @supermodel.getModel(CourseInstance, @courseInstanceID) or new CourseInstance _id: @courseInstanceID
|
|
||||||
@listenTo @courseInstance, 'sync', @onCourseInstanceSync
|
|
||||||
if @courseInstance.loaded
|
|
||||||
@onCourseInstanceSync()
|
|
||||||
else
|
|
||||||
@courseInstance = @supermodel.loadModel(@courseInstance).model
|
|
||||||
|
|
||||||
onCourseInstancesSync: ->
|
|
||||||
return if @destroyed
|
|
||||||
# console.log 'onCourseInstancesSync'
|
|
||||||
@findNextCourseInstance()
|
|
||||||
if not @courseInstance
|
|
||||||
# We are loading these to find the one we want to display.
|
|
||||||
if @courseInstances.models.length is 1
|
|
||||||
@loadCourseInstance(@courseInstances.models[0].id)
|
|
||||||
else
|
|
||||||
if @courseInstances.models.length is 0
|
|
||||||
@noCourseInstance = true
|
|
||||||
else
|
|
||||||
@noCourseInstanceSelected = true
|
|
||||||
@render()
|
|
||||||
|
|
||||||
onCourseInstanceSync: ->
|
|
||||||
return if @destroyed
|
|
||||||
# console.log 'onCourseInstanceSync'
|
|
||||||
if @courseInstance.get('classroomID')
|
|
||||||
@classroom = new Classroom({_id: @courseInstance.get('classroomID')})
|
|
||||||
@supermodel.loadModel @classroom
|
|
||||||
@singlePlayerMode = @courseInstance.get('name') is 'Single Player'
|
|
||||||
@teacherMode = @courseInstance.get('ownerID') is me.id and not @singlePlayerMode
|
|
||||||
@levelSessions = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/level_sessions", model: LevelSession, comparator: '_id' })
|
|
||||||
@listenToOnce @levelSessions, 'sync', @onLevelSessionsSync
|
|
||||||
@supermodel.loadCollection @levelSessions, 'level_sessions', cache: false
|
|
||||||
@owner = new User({_id: @courseInstance.get('ownerID')})
|
|
||||||
@supermodel.loadModel @owner
|
|
||||||
@render()
|
|
||||||
|
|
||||||
onLevelSessionsSync: ->
|
|
||||||
return if @destroyed
|
|
||||||
# console.log 'onLevelSessionsSync'
|
# console.log 'onLevelSessionsSync'
|
||||||
@memberStats = {}
|
@memberStats = {}
|
||||||
@userConceptStateMap = {}
|
@userConceptStateMap = {}
|
||||||
|
@ -179,40 +118,17 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
for concept, state of conceptStateMap
|
for concept, state of conceptStateMap
|
||||||
@conceptsCompleted[concept] ?= 0
|
@conceptsCompleted[concept] ?= 0
|
||||||
@conceptsCompleted[concept]++
|
@conceptsCompleted[concept]++
|
||||||
|
|
||||||
if @memberStats[me.id]?.totalLevelsCompleted >= _.size(@campaign.get('levels')) - 1 # Don't need to complete arena
|
|
||||||
@courseComplete = true
|
|
||||||
@loadCourseInstances() unless @courseInstances # Find the next course instance to do.
|
|
||||||
|
|
||||||
@render()
|
|
||||||
|
|
||||||
onAllCoursesSync: ->
|
|
||||||
@findNextCourseInstance()
|
|
||||||
|
|
||||||
findNextCourseInstance: ->
|
|
||||||
@nextCourseInstance = _.find @courseInstances.models, (ci) =>
|
|
||||||
# Sorted by courseID
|
|
||||||
ci.get('classroomID') is @courseInstance.get('classroomID') and ci.id isnt @courseInstance.id and ci.get('courseID') > @course.id
|
|
||||||
if @nextCourseInstance
|
|
||||||
nextCourseID = @nextCourseInstance.get('courseID')
|
|
||||||
@nextCourse = @supermodel.getModel(Course, nextCourseID) or new Course _id: nextCourseID
|
|
||||||
@nextCourse = @supermodel.loadModel(@nextCourse).model
|
|
||||||
else if @allCourses?.loaded
|
|
||||||
@nextCourse = _.find @allCourses.models, (course) => course.id > @course.id
|
|
||||||
else
|
|
||||||
@loadAllCourses()
|
|
||||||
|
|
||||||
onClickPlayLevel: (e) ->
|
onClickPlayLevel: (e) ->
|
||||||
levelSlug = $(e.target).closest('.btn-play-level').data('level-slug')
|
levelSlug = $(e.target).closest('.btn-play-level').data('level-slug')
|
||||||
levelID = $(e.target).closest('.btn-play-level').data('level-id')
|
levelID = $(e.target).closest('.btn-play-level').data('level-id')
|
||||||
level = @campaign.get('levels')[levelID]
|
level = @levels.findWhere({original: levelID})
|
||||||
if level.type is 'course-ladder'
|
if level.get('type') is 'course-ladder'
|
||||||
viewClass = 'views/ladder/LadderView'
|
viewClass = 'views/ladder/LadderView'
|
||||||
viewArgs = [{supermodel: @supermodel}, levelSlug]
|
viewArgs = [{supermodel: @supermodel}, levelSlug]
|
||||||
route = '/play/ladder/' + levelSlug
|
route = '/play/ladder/' + levelSlug
|
||||||
unless @singlePlayerMode # No league for solo courses
|
route += '/course/' + @courseInstance.id
|
||||||
route += '/course/' + @courseInstance.id
|
viewArgs = viewArgs.concat ['course', @courseInstance.id]
|
||||||
viewArgs = viewArgs.concat ['course', @courseInstance.id]
|
|
||||||
else
|
else
|
||||||
route = @getLevelURL levelSlug
|
route = @getLevelURL levelSlug
|
||||||
viewClass = 'views/play/level/PlayLevelView'
|
viewClass = 'views/play/level/PlayLevelView'
|
||||||
|
@ -222,30 +138,15 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
getLevelURL: (levelSlug) ->
|
getLevelURL: (levelSlug) ->
|
||||||
"/play/level/#{levelSlug}?course=#{@courseID}&course-instance=#{@courseInstanceID}"
|
"/play/level/#{levelSlug}?course=#{@courseID}&course-instance=#{@courseInstanceID}"
|
||||||
|
|
||||||
onClickSelectInstance: (e) ->
|
|
||||||
courseInstanceID = $('.select-instance').val()
|
|
||||||
@noCourseInstanceSelected = false
|
|
||||||
@loadCourseInstance(courseInstanceID)
|
|
||||||
|
|
||||||
getOwnerName: ->
|
getOwnerName: ->
|
||||||
return if @owner.isNew()
|
return if @owner.isNew()
|
||||||
if @owner.get('firstName') and @owner.get('lastName')
|
if @owner.get('firstName') and @owner.get('lastName')
|
||||||
return "#{@owner.get('firstName')} #{@owner.get('lastName')}"
|
return "#{@owner.get('firstName')} #{@owner.get('lastName')}"
|
||||||
@owner.get('name') or @owner.get('email')
|
@owner.get('name') or @owner.get('email')
|
||||||
|
|
||||||
onSubmitSchoolForm: (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
schoolName = @$el.find('#course-complete-school-input').val().trim()
|
|
||||||
if schoolName and schoolName isnt me.get('schoolName')
|
|
||||||
me.set 'schoolName', schoolName
|
|
||||||
me.patch()
|
|
||||||
else
|
|
||||||
storage.save 'no-school', true
|
|
||||||
@$el.find('#school-form').slideUp('slow')
|
|
||||||
|
|
||||||
getLastLevelCompleted: ->
|
getLastLevelCompleted: ->
|
||||||
lastLevelCompleted = null
|
lastLevelCompleted = null
|
||||||
for levelID in _.keys(@campaign.get('levels'))
|
for levelID in @levels.pluck('original')
|
||||||
if @userLevelStateMap?[me.id]?[levelID] is 'complete'
|
if @userLevelStateMap?[me.id]?[levelID] is 'complete'
|
||||||
lastLevelCompleted = levelID
|
lastLevelCompleted = levelID
|
||||||
return lastLevelCompleted
|
return lastLevelCompleted
|
||||||
|
|
|
@ -39,8 +39,6 @@ module.exports = class CoursesView extends RootView
|
||||||
@supermodel.trackCollection(@ownedClassrooms)
|
@supermodel.trackCollection(@ownedClassrooms)
|
||||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||||
@supermodel.loadCollection(@courses)
|
@supermodel.loadCollection(@courses)
|
||||||
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign })
|
|
||||||
@supermodel.loadCollection(@campaigns, { data: { type: 'course' }})
|
|
||||||
|
|
||||||
onCourseInstancesLoaded: ->
|
onCourseInstancesLoaded: ->
|
||||||
map = {}
|
map = {}
|
||||||
|
@ -56,26 +54,15 @@ module.exports = class CoursesView extends RootView
|
||||||
courseInstance.sessions.comparator = 'changed'
|
courseInstance.sessions.comparator = 'changed'
|
||||||
@supermodel.loadCollection(courseInstance.sessions, { data: { project: 'state.complete level.original playtime changed' }})
|
@supermodel.loadCollection(courseInstance.sessions, { data: { project: 'state.complete level.original playtime changed' }})
|
||||||
|
|
||||||
@hocCourseInstance = @courseInstances.findWhere({hourOfCode: true})
|
hocCourseInstance = @courseInstances.findWhere({hourOfCode: true})
|
||||||
if @hocCourseInstance
|
if hocCourseInstance
|
||||||
@courseInstances.remove(@hocCourseInstance)
|
@courseInstances.remove(hocCourseInstance)
|
||||||
|
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
super()
|
super()
|
||||||
if utils.getQueryVariable('_cc', false) and not me.isAnonymous()
|
if utils.getQueryVariable('_cc', false) and not me.isAnonymous()
|
||||||
@joinClass()
|
@joinClass()
|
||||||
|
|
||||||
onClickStartNewGameButton: ->
|
|
||||||
if me.isAnonymous()
|
|
||||||
@openSignUpModal()
|
|
||||||
else
|
|
||||||
modal = new ChooseLanguageModal()
|
|
||||||
@openModalView(modal)
|
|
||||||
@listenToOnce modal, 'set-language', =>
|
|
||||||
@startHourOfCodePlay()
|
|
||||||
application.tracker?.trackEvent 'Automatic start hour of code play', category: 'Courses', label: 'set language'
|
|
||||||
application.tracker?.trackEvent 'Start New Game', category: 'Courses'
|
|
||||||
|
|
||||||
onClickLogInButton: ->
|
onClickLogInButton: ->
|
||||||
modal = new StudentLogInModal()
|
modal = new StudentLogInModal()
|
||||||
@openModalView(modal)
|
@openModalView(modal)
|
||||||
|
@ -85,21 +72,8 @@ module.exports = class CoursesView extends RootView
|
||||||
openSignUpModal: ->
|
openSignUpModal: ->
|
||||||
modal = new StudentSignUpModal({ willPlay: true })
|
modal = new StudentSignUpModal({ willPlay: true })
|
||||||
@openModalView(modal)
|
@openModalView(modal)
|
||||||
modal.once 'click-skip-link', (=>
|
|
||||||
@startHourOfCodePlay()
|
|
||||||
application.tracker?.trackEvent 'Automatic start hour of code play', category: 'Courses', label: 'skip link'
|
|
||||||
), @
|
|
||||||
application.tracker?.trackEvent 'Started Student Signup', category: 'Courses'
|
application.tracker?.trackEvent 'Started Student Signup', category: 'Courses'
|
||||||
|
|
||||||
startHourOfCodePlay: ->
|
|
||||||
@$('#main-content').hide()
|
|
||||||
@$('#begin-hoc-area').removeClass('hide')
|
|
||||||
hocCourseInstance = new CourseInstance()
|
|
||||||
hocCourseInstance.upsertForHOC()
|
|
||||||
@listenToOnce hocCourseInstance, 'sync', ->
|
|
||||||
url = hocCourseInstance.firstLevelURL()
|
|
||||||
app.router.navigate(url, { trigger: true })
|
|
||||||
|
|
||||||
onSubmitJoinClassForm: (e) ->
|
onSubmitJoinClassForm: (e) ->
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@joinClass()
|
@joinClass()
|
||||||
|
|
|
@ -14,7 +14,6 @@ Users = require 'collections/Users'
|
||||||
Courses = require 'collections/Courses'
|
Courses = require 'collections/Courses'
|
||||||
CourseInstance = require 'models/CourseInstance'
|
CourseInstance = require 'models/CourseInstance'
|
||||||
CourseInstances = require 'collections/CourseInstances'
|
CourseInstances = require 'collections/CourseInstances'
|
||||||
Campaigns = require 'collections/Campaigns'
|
|
||||||
|
|
||||||
module.exports = class TeacherClassView extends RootView
|
module.exports = class TeacherClassView extends RootView
|
||||||
id: 'teacher-class-view'
|
id: 'teacher-class-view'
|
||||||
|
@ -62,10 +61,6 @@ module.exports = class TeacherClassView extends RootView
|
||||||
@courses.fetch()
|
@courses.fetch()
|
||||||
@supermodel.trackCollection(@courses)
|
@supermodel.trackCollection(@courses)
|
||||||
|
|
||||||
@campaigns = new Campaigns()
|
|
||||||
@campaigns.fetchByType('course')
|
|
||||||
@supermodel.trackCollection(@campaigns)
|
|
||||||
|
|
||||||
@courseInstances = new CourseInstances()
|
@courseInstances = new CourseInstances()
|
||||||
@courseInstances.fetchByOwner(me.id)
|
@courseInstances.fetchByOwner(me.id)
|
||||||
@supermodel.trackCollection(@courseInstances)
|
@supermodel.trackCollection(@courseInstances)
|
||||||
|
@ -76,15 +71,15 @@ module.exports = class TeacherClassView extends RootView
|
||||||
@classCode = @classroom.get('codeCamel') or @classroom.get('code')
|
@classCode = @classroom.get('codeCamel') or @classroom.get('code')
|
||||||
@joinURL = document.location.origin + "/courses?_cc=" + @classCode
|
@joinURL = document.location.origin + "/courses?_cc=" + @classCode
|
||||||
|
|
||||||
@earliestIncompleteLevel = helper.calculateEarliestIncomplete(@classroom, @courses, @campaigns, @courseInstances, @students)
|
@earliestIncompleteLevel = helper.calculateEarliestIncomplete(@classroom, @courses, @courseInstances, @students)
|
||||||
@latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @campaigns, @courseInstances, @students)
|
@latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @courseInstances, @students)
|
||||||
for student in @students.models
|
for student in @students.models
|
||||||
# TODO: this is a weird hack
|
# TODO: this is a weird hack
|
||||||
studentsStub = new Users([ student ])
|
studentsStub = new Users([ student ])
|
||||||
student.latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @campaigns, @courseInstances, studentsStub)
|
student.latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @courseInstances, studentsStub)
|
||||||
|
|
||||||
classroomsStub = new Classrooms([ @classroom ])
|
classroomsStub = new Classrooms([ @classroom ])
|
||||||
@progressData = helper.calculateAllProgress(classroomsStub, @courses, @campaigns, @courseInstances, @students)
|
@progressData = helper.calculateAllProgress(classroomsStub, @courses, @courseInstances, @students)
|
||||||
# @conceptData = helper.calculateConceptsCovered(classroomsStub, @courses, @campaigns, @courseInstances, @students)
|
# @conceptData = helper.calculateConceptsCovered(classroomsStub, @courses, @campaigns, @courseInstances, @students)
|
||||||
|
|
||||||
@selectedCourse = @courses.first()
|
@selectedCourse = @courses.first()
|
||||||
|
|
|
@ -41,10 +41,6 @@ module.exports = class TeacherClassesView extends RootView
|
||||||
@courses.fetch()
|
@courses.fetch()
|
||||||
@supermodel.trackCollection(@courses)
|
@supermodel.trackCollection(@courses)
|
||||||
|
|
||||||
@campaigns = new Campaigns()
|
|
||||||
@campaigns.fetchByType('course')
|
|
||||||
@supermodel.trackCollection(@campaigns)
|
|
||||||
|
|
||||||
@courseInstances = new CourseInstances()
|
@courseInstances = new CourseInstances()
|
||||||
@courseInstances.fetchByOwner(me.id)
|
@courseInstances.fetchByOwner(me.id)
|
||||||
@supermodel.trackCollection(@courseInstances)
|
@supermodel.trackCollection(@courseInstances)
|
||||||
|
@ -62,7 +58,7 @@ module.exports = class TeacherClassesView extends RootView
|
||||||
})
|
})
|
||||||
|
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
helper.calculateDots(@classrooms, @courses, @courseInstances, @campaigns)
|
helper.calculateDots(@classrooms, @courses, @courseInstances)
|
||||||
super()
|
super()
|
||||||
|
|
||||||
onClickEditClassroom: (e) ->
|
onClickEditClassroom: (e) ->
|
||||||
|
|
|
@ -2,7 +2,6 @@ ModalView = require 'views/core/ModalView'
|
||||||
template = require 'templates/play/level/modal/course-victory-modal'
|
template = require 'templates/play/level/modal/course-victory-modal'
|
||||||
Achievements = require 'collections/Achievements'
|
Achievements = require 'collections/Achievements'
|
||||||
Level = require 'models/Level'
|
Level = require 'models/Level'
|
||||||
Campaign = require 'models/Campaign'
|
|
||||||
Course = require 'models/Course'
|
Course = require 'models/Course'
|
||||||
ThangType = require 'models/ThangType'
|
ThangType = require 'models/ThangType'
|
||||||
ThangTypes = require 'collections/ThangTypes'
|
ThangTypes = require 'collections/ThangTypes'
|
||||||
|
@ -11,6 +10,7 @@ EarnedAchievement = require 'models/EarnedAchievement'
|
||||||
LocalMongo = require 'lib/LocalMongo'
|
LocalMongo = require 'lib/LocalMongo'
|
||||||
ProgressView = require './ProgressView'
|
ProgressView = require './ProgressView'
|
||||||
NewItemView = require './NewItemView'
|
NewItemView = require './NewItemView'
|
||||||
|
Classroom = require 'models/Classroom'
|
||||||
utils = require 'core/utils'
|
utils = require 'core/utils'
|
||||||
|
|
||||||
module.exports = class CourseVictoryModal extends ModalView
|
module.exports = class CourseVictoryModal extends ModalView
|
||||||
|
@ -28,6 +28,9 @@ module.exports = class CourseVictoryModal extends ModalView
|
||||||
@level = options.level
|
@level = options.level
|
||||||
@newItems = new ThangTypes()
|
@newItems = new ThangTypes()
|
||||||
@newHeroes = new ThangTypes()
|
@newHeroes = new ThangTypes()
|
||||||
|
|
||||||
|
@classroom = new Classroom()
|
||||||
|
@supermodel.trackRequest(@classroom.fetchForCourseInstance(@courseInstanceID))
|
||||||
|
|
||||||
@achievements = options.achievements
|
@achievements = options.achievements
|
||||||
if not @achievements
|
if not @achievements
|
||||||
|
@ -39,22 +42,13 @@ module.exports = class CourseVictoryModal extends ModalView
|
||||||
@onAchievementsLoaded()
|
@onAchievementsLoaded()
|
||||||
|
|
||||||
@playSound 'victory'
|
@playSound 'victory'
|
||||||
@nextLevel = options.nextLevel
|
@nextLevel = new Level()
|
||||||
if (nextLevel = @level.get('nextLevel')) and not @nextLevel
|
@nextLevelRequest = @supermodel.trackRequest @nextLevel.fetchNextForCourse(@level.get('original'), @courseInstanceID)
|
||||||
@nextLevel = new Level().setURL "/db/level/#{nextLevel.original}/version/#{nextLevel.majorVersion}"
|
|
||||||
@nextLevel = @supermodel.loadModel(@nextLevel).model
|
|
||||||
|
|
||||||
@campaign = new Campaign()
|
|
||||||
@course = options.course
|
@course = options.course
|
||||||
if @courseID and not @course
|
if @courseID and not @course
|
||||||
@course = new Course().setURL "/db/course/#{@courseID}"
|
@course = new Course().setURL "/db/course/#{@courseID}"
|
||||||
@course = @supermodel.loadModel(@course).model
|
@course = @supermodel.loadModel(@course).model
|
||||||
if @course.loading
|
|
||||||
@listenToOnce @course, 'sync', @onCourseLoaded
|
|
||||||
else
|
|
||||||
@onCourseLoaded()
|
|
||||||
else if @course
|
|
||||||
@onCourseLoaded()
|
|
||||||
|
|
||||||
if @courseInstanceID
|
if @courseInstanceID
|
||||||
@levelSessions = new LevelSessions()
|
@levelSessions = new LevelSessions()
|
||||||
|
@ -63,10 +57,10 @@ module.exports = class CourseVictoryModal extends ModalView
|
||||||
data: { project: 'state.complete level.original playtime changed' }
|
data: { project: 'state.complete level.original playtime changed' }
|
||||||
}).model
|
}).model
|
||||||
|
|
||||||
|
onResourceLoadFailed: (e) ->
|
||||||
onCourseLoaded: ->
|
if e.resource.jqxhr is @nextLevelRequest
|
||||||
@campaign.set('_id', @course.get('campaignID'))
|
return
|
||||||
@campaign = @supermodel.loadModel(@campaign).model
|
super(arguments...)
|
||||||
|
|
||||||
|
|
||||||
onAchievementsLoaded: ->
|
onAchievementsLoaded: ->
|
||||||
|
@ -135,7 +129,7 @@ module.exports = class CourseVictoryModal extends ModalView
|
||||||
level: @level
|
level: @level
|
||||||
nextLevel: @nextLevel
|
nextLevel: @nextLevel
|
||||||
course: @course
|
course: @course
|
||||||
campaign: @campaign
|
classroom: @classroom
|
||||||
levelSessions: @levelSessions
|
levelSessions: @levelSessions
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@ module.exports = class ProgressView extends CocoView
|
||||||
initialize: (options) ->
|
initialize: (options) ->
|
||||||
@level = options.level
|
@level = options.level
|
||||||
@course = options.course
|
@course = options.course
|
||||||
|
@classroom = options.classroom
|
||||||
@nextLevel = options.nextLevel
|
@nextLevel = options.nextLevel
|
||||||
@campaign = options.campaign
|
|
||||||
@levelSessions = options.levelSessions
|
@levelSessions = options.levelSessions
|
||||||
|
|
||||||
onClickDoneButton: ->
|
onClickDoneButton: ->
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
load('bower_components/lodash/dist/lodash.js');
|
||||||
|
|
||||||
|
var courses = db.courses.find({}).sort({_id:1}).toArray();
|
||||||
|
var ids = _.pluck(courses, 'campaignID');
|
||||||
|
var campaigns = db.campaigns.find({_id: {$in: ids}}).toArray();
|
||||||
|
var campaignMap = {};
|
||||||
|
for (var campaignIndex in campaigns) {
|
||||||
|
var campaign = campaigns[campaignIndex];
|
||||||
|
campaignMap[campaign._id.str] = campaign;
|
||||||
|
}
|
||||||
|
var coursesData = [];
|
||||||
|
|
||||||
|
for (var courseIndex in courses) {
|
||||||
|
var course = courses[courseIndex];
|
||||||
|
var courseData = { _id: course._id, levels: [] };
|
||||||
|
var campaign = campaignMap[course.campaignID.str];
|
||||||
|
var levels = _.values(campaign.levels);
|
||||||
|
levels = _.sortBy(levels, 'campaignIndex');
|
||||||
|
_.forEach(levels, function(level) {
|
||||||
|
levelData = { original: ObjectId(level.original) };
|
||||||
|
_.extend(levelData, _.pick(level, 'type', 'slug', 'name'));
|
||||||
|
courseData.levels.push(levelData);
|
||||||
|
});
|
||||||
|
coursesData.push(courseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
print('constructed', JSON.stringify(coursesData, null, '\t'));
|
||||||
|
|
||||||
|
db.classrooms.update(
|
||||||
|
{}, // Set all
|
||||||
|
//{courses: {$exists: false}}, // Set all w/out values
|
||||||
|
{$set: {courses: coursesData}},
|
||||||
|
{multi: true}
|
||||||
|
);
|
|
@ -155,6 +155,8 @@ module.exports =
|
||||||
tv4 = require('tv4').tv4
|
tv4 = require('tv4').tv4
|
||||||
result = tv4.validateMultiple(obj, doc.schema.statics.jsonSchema)
|
result = tv4.validateMultiple(obj, doc.schema.statics.jsonSchema)
|
||||||
if not result.valid
|
if not result.valid
|
||||||
|
prunedErrors = (_.omit(error, 'stack') for error in result.errors)
|
||||||
|
winston.debug('Validation errors: ', JSON.stringify(prunedErrors, null, '\t'))
|
||||||
throw new errors.UnprocessableEntity('JSON-schema validation failed', { validationErrors: result.errors })
|
throw new errors.UnprocessableEntity('JSON-schema validation failed', { validationErrors: result.errors })
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,16 @@ module.exports =
|
||||||
campaign = yield database.getDocFromHandle(req, Campaign)
|
campaign = yield database.getDocFromHandle(req, Campaign)
|
||||||
if not campaign
|
if not campaign
|
||||||
throw new errors.NotFound('Campaign not found.')
|
throw new errors.NotFound('Campaign not found.')
|
||||||
|
levelsBefore = _.keys(campaign.get('levels'))
|
||||||
hasPermission = req.user.isAdmin()
|
hasPermission = req.user.isAdmin()
|
||||||
unless hasPermission or database.isJustFillingTranslations(req, campaign)
|
unless hasPermission or database.isJustFillingTranslations(req, campaign)
|
||||||
throw new errors.Forbidden('Must be an admin or submitting translations to edit a campaign')
|
throw new errors.Forbidden('Must be an admin or submitting translations to edit a campaign')
|
||||||
|
|
||||||
database.assignBody(req, campaign)
|
database.assignBody(req, campaign)
|
||||||
database.validateDoc(campaign)
|
database.validateDoc(campaign)
|
||||||
|
levelsAfter = _.keys(campaign.get('levels'))
|
||||||
|
if not _.isEqual(levelsBefore, levelsAfter)
|
||||||
|
campaign.set('levelsUpdated', new Date())
|
||||||
campaign = yield campaign.save()
|
campaign = yield campaign.save()
|
||||||
res.status(200).send(campaign.toObject())
|
res.status(200).send(campaign.toObject())
|
||||||
docLink = "http://codecombat.com#{req.headers['x-current-path']}"
|
docLink = "http://codecombat.com#{req.headers['x-current-path']}"
|
||||||
|
|
|
@ -6,6 +6,9 @@ Promise = require 'bluebird'
|
||||||
database = require '../commons/database'
|
database = require '../commons/database'
|
||||||
mongoose = require 'mongoose'
|
mongoose = require 'mongoose'
|
||||||
Classroom = require '../models/Classroom'
|
Classroom = require '../models/Classroom'
|
||||||
|
Course = require '../models/Course'
|
||||||
|
Campaign = require '../models/Campaign'
|
||||||
|
Level = require '../models/Level'
|
||||||
parse = require '../commons/parse'
|
parse = require '../commons/parse'
|
||||||
LevelSession = require '../models/LevelSession'
|
LevelSession = require '../models/LevelSession'
|
||||||
User = require '../models/User'
|
User = require '../models/User'
|
||||||
|
@ -28,6 +31,50 @@ module.exports =
|
||||||
classrooms = (classroom.toObject({req: req}) for classroom in classrooms)
|
classrooms = (classroom.toObject({req: req}) for classroom in classrooms)
|
||||||
res.status(200).send(classrooms)
|
res.status(200).send(classrooms)
|
||||||
|
|
||||||
|
fetchAllLevels: wrap (req, res, next) ->
|
||||||
|
classroom = yield database.getDocFromHandle(req, Classroom)
|
||||||
|
if not classroom
|
||||||
|
throw new errors.NotFound('Classroom not found.')
|
||||||
|
|
||||||
|
levelOriginals = []
|
||||||
|
for course in classroom.get('courses') or []
|
||||||
|
for level in course.levels
|
||||||
|
levelOriginals.push(level.original)
|
||||||
|
|
||||||
|
levels = yield Level.find({ original: { $in: levelOriginals }, slug: { $exists: true }}).select(parse.getProjectFromReq(req))
|
||||||
|
levels = (level.toObject({ req: req }) for level in levels)
|
||||||
|
|
||||||
|
# maintain course order
|
||||||
|
levelMap = {}
|
||||||
|
for level in levels
|
||||||
|
levelMap[level.original] = level
|
||||||
|
levels = (levelMap[levelOriginal.toString()] for levelOriginal in levelOriginals)
|
||||||
|
|
||||||
|
res.status(200).send(levels)
|
||||||
|
|
||||||
|
fetchLevelsForCourse: wrap (req, res) ->
|
||||||
|
classroom = yield database.getDocFromHandle(req, Classroom)
|
||||||
|
if not classroom
|
||||||
|
throw new errors.NotFound('Classroom not found.')
|
||||||
|
|
||||||
|
levelOriginals = []
|
||||||
|
for course in classroom.get('courses') or []
|
||||||
|
if course._id.toString() isnt req.params.courseID
|
||||||
|
continue
|
||||||
|
for level in course.levels
|
||||||
|
levelOriginals.push(level.original)
|
||||||
|
|
||||||
|
levels = yield Level.find({ original: { $in: levelOriginals }, slug: { $exists: true }}).select(parse.getProjectFromReq(req))
|
||||||
|
levels = (level.toObject({ req: req }) for level in levels)
|
||||||
|
|
||||||
|
# maintain course order
|
||||||
|
levelMap = {}
|
||||||
|
for level in levels
|
||||||
|
levelMap[level.original] = level
|
||||||
|
levels = (levelMap[levelOriginal.toString()] for levelOriginal in levelOriginals)
|
||||||
|
|
||||||
|
res.status(200).send(levels)
|
||||||
|
|
||||||
fetchMemberSessions: wrap (req, res, next) ->
|
fetchMemberSessions: wrap (req, res, next) ->
|
||||||
throw new errors.Unauthorized() unless req.user
|
throw new errors.Unauthorized() unless req.user
|
||||||
memberLimit = parse.getLimitFromReq(req, {default: 10, max: 100, param: 'memberLimit'})
|
memberLimit = parse.getLimitFromReq(req, {default: 10, max: 100, param: 'memberLimit'})
|
||||||
|
@ -71,6 +118,26 @@ module.exports =
|
||||||
classroom.set 'ownerID', req.user._id
|
classroom.set 'ownerID', req.user._id
|
||||||
classroom.set 'members', []
|
classroom.set 'members', []
|
||||||
database.assignBody(req, classroom)
|
database.assignBody(req, classroom)
|
||||||
|
|
||||||
|
# copy over data from how courses are right now
|
||||||
|
courses = yield Course.find()
|
||||||
|
campaigns = yield Campaign.find({_id: {$in: (course.get('campaignID') for course in courses)}})
|
||||||
|
campaignMap = {}
|
||||||
|
campaignMap[campaign.id] = campaign for campaign in campaigns
|
||||||
|
coursesData = []
|
||||||
|
for course in courses
|
||||||
|
courseData = { _id: course._id, levels: [] }
|
||||||
|
campaign = campaignMap[course.get('campaignID').toString()]
|
||||||
|
levels = _.values(campaign.get('levels'))
|
||||||
|
levels = _.sortBy(levels, 'campaignIndex')
|
||||||
|
for level in levels
|
||||||
|
levelData = { original: mongoose.Types.ObjectId(level.original) }
|
||||||
|
_.extend(levelData, _.pick(level, 'type', 'slug', 'name'))
|
||||||
|
courseData.levels.push(levelData)
|
||||||
|
coursesData.push(courseData)
|
||||||
|
classroom.set('courses', coursesData)
|
||||||
|
|
||||||
|
# finish
|
||||||
database.validateDoc(classroom)
|
database.validateDoc(classroom)
|
||||||
classroom = yield classroom.save()
|
classroom = yield classroom.save()
|
||||||
res.status(201).send(classroom.toObject({req: req}))
|
res.status(201).send(classroom.toObject({req: req}))
|
|
@ -8,6 +8,8 @@ CourseInstance = require '../models/CourseInstance'
|
||||||
Classroom = require '../models/Classroom'
|
Classroom = require '../models/Classroom'
|
||||||
Course = require '../models/Course'
|
Course = require '../models/Course'
|
||||||
User = require '../models/User'
|
User = require '../models/User'
|
||||||
|
Level = require '../models/Level'
|
||||||
|
parse = require '../commons/parse'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
addMembers: wrap (req, res) ->
|
addMembers: wrap (req, res) ->
|
||||||
|
@ -63,3 +65,61 @@ module.exports =
|
||||||
)
|
)
|
||||||
|
|
||||||
res.status(200).send(courseInstance.toObject({ req }))
|
res.status(200).send(courseInstance.toObject({ req }))
|
||||||
|
|
||||||
|
|
||||||
|
fetchNextLevel: wrap (req, res) ->
|
||||||
|
levelOriginal = req.params.levelOriginal
|
||||||
|
if not database.isID(levelOriginal)
|
||||||
|
throw new errors.UnprocessableEntity('Invalid level original ObjectId')
|
||||||
|
|
||||||
|
courseInstance = yield database.getDocFromHandle(req, CourseInstance)
|
||||||
|
if not courseInstance
|
||||||
|
throw new errors.NotFound('Course Instance not found.')
|
||||||
|
courseID = courseInstance.get('courseID')
|
||||||
|
|
||||||
|
classroom = yield Classroom.findById courseInstance.get('classroomID')
|
||||||
|
if not classroom
|
||||||
|
throw new errors.NotFound('Classroom not found.')
|
||||||
|
|
||||||
|
nextLevelOriginal = null
|
||||||
|
foundLevelOriginal = false
|
||||||
|
for course in classroom.get('courses') or []
|
||||||
|
if not courseID.equals(course._id)
|
||||||
|
continue
|
||||||
|
for level, index in course.levels
|
||||||
|
if level.original.toString() is levelOriginal
|
||||||
|
foundLevelOriginal = true
|
||||||
|
nextLevelOriginal = course.levels[index+1]?.original
|
||||||
|
break
|
||||||
|
|
||||||
|
if not foundLevelOriginal
|
||||||
|
throw new errors.NotFound('Level original ObjectId not found in Classroom courses')
|
||||||
|
|
||||||
|
if not nextLevelOriginal
|
||||||
|
throw new errors.NotFound('No more levels in that course')
|
||||||
|
|
||||||
|
dbq = Level.findOne({original: mongoose.Types.ObjectId(nextLevelOriginal)})
|
||||||
|
dbq.sort({ 'version.major': -1, 'version.minor': -1 })
|
||||||
|
dbq.select(parse.getProjectFromReq(req))
|
||||||
|
level = yield dbq
|
||||||
|
level = level.toObject({req: req})
|
||||||
|
res.status(200).send(level)
|
||||||
|
|
||||||
|
|
||||||
|
fetchClassroom: wrap (req, res) ->
|
||||||
|
courseInstance = yield database.getDocFromHandle(req, CourseInstance)
|
||||||
|
if not courseInstance
|
||||||
|
throw new errors.NotFound('Course Instance not found.')
|
||||||
|
|
||||||
|
classroom = yield Classroom.findById(courseInstance.get('classroomID')).select(parse.getProjectFromReq(req))
|
||||||
|
if not classroom
|
||||||
|
throw new errors.NotFound('Classroom not found.')
|
||||||
|
|
||||||
|
isOwner = classroom.get('ownerID')?.equals req.user?._id
|
||||||
|
isMember = _.any(classroom.get('members') or [], (memberID) -> memberID.equals(req.user.get('_id')))
|
||||||
|
if not (isOwner or isMember)
|
||||||
|
throw new errors.Forbidden('You do not have access to this classroom')
|
||||||
|
|
||||||
|
classroom = classroom.toObject({req: req})
|
||||||
|
|
||||||
|
res.status(200).send(classroom)
|
|
@ -34,6 +34,11 @@ CampaignSchema.statics.updateAdjacentCampaigns = (savedCampaign) ->
|
||||||
Campaign.findByIdAndUpdate campaign._id, {$set: {adjacentCampaigns: acs}}, (err, doc) ->
|
Campaign.findByIdAndUpdate campaign._id, {$set: {adjacentCampaigns: acs}}, (err, doc) ->
|
||||||
return log.error "Couldn't save updated adjacent campaign because of #{err}" if err
|
return log.error "Couldn't save updated adjacent campaign because of #{err}" if err
|
||||||
|
|
||||||
|
CampaignSchema.pre 'save', (done) ->
|
||||||
|
if not @get('levelsUpdated')
|
||||||
|
@set('levelsUpdated', @_id.getTimestamp())
|
||||||
|
done()
|
||||||
|
|
||||||
CampaignSchema.post 'save', -> @constructor.updateAdjacentCampaigns @
|
CampaignSchema.post 'save', -> @constructor.updateAdjacentCampaigns @
|
||||||
|
|
||||||
CampaignSchema.statics.jsonSchema = jsonSchema
|
CampaignSchema.statics.jsonSchema = jsonSchema
|
||||||
|
|
|
@ -5,6 +5,7 @@ plugins = require '../plugins/plugins'
|
||||||
User = require './User'
|
User = require './User'
|
||||||
jsonSchema = require '../../app/schemas/models/classroom.schema.coffee'
|
jsonSchema = require '../../app/schemas/models/classroom.schema.coffee'
|
||||||
utils = require '../lib/utils'
|
utils = require '../lib/utils'
|
||||||
|
co = require 'co'
|
||||||
|
|
||||||
ClassroomSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref}
|
ClassroomSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref}
|
||||||
|
|
||||||
|
@ -52,6 +53,9 @@ ClassroomSchema.statics.jsonSchema = jsonSchema
|
||||||
|
|
||||||
ClassroomSchema.set('toObject', {
|
ClassroomSchema.set('toObject', {
|
||||||
transform: (doc, ret, options) ->
|
transform: (doc, ret, options) ->
|
||||||
|
# TODO: Remove this once classrooms are populated. This is only for when we are testing locked course content.
|
||||||
|
if not ret.courses
|
||||||
|
ret.courses = coursesData
|
||||||
return ret unless options.req
|
return ret unless options.req
|
||||||
user = options.req.user
|
user = options.req.user
|
||||||
unless user and (user.isAdmin() or user._id.equals(doc.get('ownerID')))
|
unless user and (user.isAdmin() or user._id.equals(doc.get('ownerID')))
|
||||||
|
@ -61,3 +65,26 @@ ClassroomSchema.set('toObject', {
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = Classroom = mongoose.model 'classroom', ClassroomSchema, 'classrooms'
|
module.exports = Classroom = mongoose.model 'classroom', ClassroomSchema, 'classrooms'
|
||||||
|
|
||||||
|
coursesData = []
|
||||||
|
|
||||||
|
co ->
|
||||||
|
console.log 'Populating courses data...'
|
||||||
|
Course = require './Course'
|
||||||
|
Campaign = require './Campaign'
|
||||||
|
courses = yield Course.find()
|
||||||
|
campaigns = yield Campaign.find({_id: {$in: (course.get('campaignID') for course in courses)}})
|
||||||
|
campaignMap = {}
|
||||||
|
campaignMap[campaign.id] = campaign for campaign in campaigns
|
||||||
|
coursesData = []
|
||||||
|
for course in courses
|
||||||
|
courseData = { _id: course._id, levels: [] }
|
||||||
|
campaign = campaignMap[course.get('campaignID').toString()]
|
||||||
|
levels = _.values(campaign.get('levels'))
|
||||||
|
levels = _.sortBy(levels, 'campaignIndex')
|
||||||
|
for level in levels
|
||||||
|
levelData = { original: mongoose.Types.ObjectId(level.original) }
|
||||||
|
_.extend(levelData, _.pick(level, 'type', 'slug', 'name'))
|
||||||
|
courseData.levels.push(levelData)
|
||||||
|
coursesData.push(courseData)
|
||||||
|
console.log 'Populated courses data.'
|
|
@ -46,6 +46,8 @@ module.exports.setup = (app) ->
|
||||||
|
|
||||||
app.post('/db/classroom', mw.classrooms.post)
|
app.post('/db/classroom', mw.classrooms.post)
|
||||||
app.get('/db/classroom', mw.classrooms.getByOwner)
|
app.get('/db/classroom', mw.classrooms.getByOwner)
|
||||||
|
app.get('/db/classroom/:handle/levels', mw.classrooms.fetchAllLevels)
|
||||||
|
app.get('/db/classroom/:handle/courses/:courseID/levels', mw.classrooms.fetchLevelsForCourse)
|
||||||
app.get('/db/classroom/:handle/member-sessions', mw.classrooms.fetchMemberSessions)
|
app.get('/db/classroom/:handle/member-sessions', mw.classrooms.fetchMemberSessions)
|
||||||
app.get('/db/classroom/:handle/members', mw.classrooms.fetchMembers) # TODO: Use mw.auth?
|
app.get('/db/classroom/:handle/members', mw.classrooms.fetchMembers) # TODO: Use mw.auth?
|
||||||
app.get('/db/classroom/:handle', mw.auth.checkLoggedIn()) # TODO: Finish migrating route, adding now so 401 is returned
|
app.get('/db/classroom/:handle', mw.auth.checkLoggedIn()) # TODO: Finish migrating route, adding now so 401 is returned
|
||||||
|
@ -58,7 +60,9 @@ module.exports.setup = (app) ->
|
||||||
app.get('/db/course', mw.rest.get(Course))
|
app.get('/db/course', mw.rest.get(Course))
|
||||||
app.get('/db/course/:handle', mw.rest.getByHandle(Course))
|
app.get('/db/course/:handle', mw.rest.getByHandle(Course))
|
||||||
|
|
||||||
|
app.get('/db/course_instance/:handle/levels/:levelOriginal/next', mw.courseInstances.fetchNextLevel)
|
||||||
app.post('/db/course_instance/:handle/members', mw.auth.checkLoggedIn(), mw.courseInstances.addMembers)
|
app.post('/db/course_instance/:handle/members', mw.auth.checkLoggedIn(), mw.courseInstances.addMembers)
|
||||||
|
app.get('/db/course_instance/:handle/classroom', mw.auth.checkLoggedIn(), mw.courseInstances.fetchClassroom)
|
||||||
|
|
||||||
app.delete('/db/user/:handle', mw.users.removeFromClassrooms)
|
app.delete('/db/user/:handle', mw.users.removeFromClassrooms)
|
||||||
app.get('/db/user', mw.users.fetchByGPlusID, mw.users.fetchByFacebookID)
|
app.get('/db/user', mw.users.fetchByGPlusID, mw.users.fetchByFacebookID)
|
||||||
|
|
|
@ -37,6 +37,7 @@ User = require '../../../server/models/User'
|
||||||
request = require '../request'
|
request = require '../request'
|
||||||
utils = require '../utils'
|
utils = require '../utils'
|
||||||
slack = require '../../../server/slack'
|
slack = require '../../../server/slack'
|
||||||
|
Promise = require 'bluebird'
|
||||||
|
|
||||||
describe 'PUT /db/campaign', ->
|
describe 'PUT /db/campaign', ->
|
||||||
beforeEach utils.wrap (done) ->
|
beforeEach utils.wrap (done) ->
|
||||||
|
@ -44,6 +45,7 @@ describe 'PUT /db/campaign', ->
|
||||||
admin = yield utils.initAdmin()
|
admin = yield utils.initAdmin()
|
||||||
yield utils.loginUser(admin)
|
yield utils.loginUser(admin)
|
||||||
[res, body] = yield request.postAsync { uri: campaignURL, json: campaign }
|
[res, body] = yield request.postAsync { uri: campaignURL, json: campaign }
|
||||||
|
@levelsUpdated = body.levelsUpdated
|
||||||
@campaign = yield Campaign.findById(body._id)
|
@campaign = yield Campaign.findById(body._id)
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
@ -75,6 +77,16 @@ describe 'PUT /db/campaign', ->
|
||||||
[res, body] = yield request.putAsync { uri: campaignURL+'/'+@campaign.id, json: { name: 'A new name' } }
|
[res, body] = yield request.putAsync { uri: campaignURL+'/'+@campaign.id, json: { name: 'A new name' } }
|
||||||
expect(slack.sendSlackMessage).toHaveBeenCalled()
|
expect(slack.sendSlackMessage).toHaveBeenCalled()
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
it 'sets campaign.levelsUpdated to now iff levels are changed', utils.wrap (done) ->
|
||||||
|
data = {name: 'whatever'}
|
||||||
|
[res, body] = yield request.putAsync { uri: campaignURL+'/'+@campaign.id, json: data }
|
||||||
|
expect(body.levelsUpdated).toBe(@levelsUpdated)
|
||||||
|
yield new Promise((resolve) -> setTimeout(resolve, 10))
|
||||||
|
data = {levels: {'a': {original: 'a'}}}
|
||||||
|
[res, body] = yield request.putAsync { uri: campaignURL+'/'+@campaign.id, json: data }
|
||||||
|
expect(body.levelsUpdated).not.toBe(@levelsUpdated)
|
||||||
|
done()
|
||||||
|
|
||||||
describe '/db/campaign', ->
|
describe '/db/campaign', ->
|
||||||
it 'prepares the db first', (done) ->
|
it 'prepares the db first', (done) ->
|
||||||
|
|
|
@ -8,6 +8,8 @@ request = require '../request'
|
||||||
requestAsync = Promise.promisify(request, {multiArgs: true})
|
requestAsync = Promise.promisify(request, {multiArgs: true})
|
||||||
User = require '../../../server/models/User'
|
User = require '../../../server/models/User'
|
||||||
Classroom = require '../../../server/models/Classroom'
|
Classroom = require '../../../server/models/Classroom'
|
||||||
|
Course = require '../../../server/models/Course'
|
||||||
|
Campaign = require '../../../server/models/Campaign'
|
||||||
LevelSession = require '../../../server/models/LevelSession'
|
LevelSession = require '../../../server/models/LevelSession'
|
||||||
Level = require '../../../server/models/Level'
|
Level = require '../../../server/models/Level'
|
||||||
|
|
||||||
|
@ -60,7 +62,28 @@ describe 'GET /db/classroom/:id', ->
|
||||||
describe 'POST /db/classroom', ->
|
describe 'POST /db/classroom', ->
|
||||||
|
|
||||||
beforeEach utils.wrap (done) ->
|
beforeEach utils.wrap (done) ->
|
||||||
yield utils.clearModels [User, Classroom]
|
yield utils.clearModels [User, Classroom, Course, Level, Campaign]
|
||||||
|
admin = yield utils.initAdmin()
|
||||||
|
yield utils.loginUser(admin)
|
||||||
|
levelJSONA = { name: 'Level A', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSONA})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@levelA = yield Level.findById(res.body._id)
|
||||||
|
levelJSONB = { name: 'Level B', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSONB})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@levelB = yield Level.findById(res.body._id)
|
||||||
|
campaignJSON = { name: 'Campaign', levels: {} }
|
||||||
|
paredLevelB = _.pick(@levelB.toObject(), 'name', 'original', 'type', 'slug')
|
||||||
|
paredLevelB.campaignIndex = 1
|
||||||
|
campaignJSON.levels[@levelB.get('original').toString()] = paredLevelB
|
||||||
|
paredLevelA = _.pick(@levelA.toObject(), 'name', 'original', 'type', 'slug')
|
||||||
|
paredLevelA.campaignIndex = 0
|
||||||
|
campaignJSON.levels[@levelA.get('original').toString()] = paredLevelA
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSON})
|
||||||
|
@campaign = yield Campaign.findById(res.body._id)
|
||||||
|
@course = Course({name: 'Course', campaignID: @campaign._id})
|
||||||
|
yield @course.save()
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'creates a new classroom for the given user with teacher role', utils.wrap (done) ->
|
it 'creates a new classroom for the given user with teacher role', utils.wrap (done) ->
|
||||||
|
@ -75,6 +98,7 @@ describe 'POST /db/classroom', ->
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'returns 401 for anonymous users', utils.wrap (done) ->
|
it 'returns 401 for anonymous users', utils.wrap (done) ->
|
||||||
|
yield utils.logout()
|
||||||
data = { name: 'Classroom 2' }
|
data = { name: 'Classroom 2' }
|
||||||
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||||
expect(res.statusCode).toBe(401)
|
expect(res.statusCode).toBe(401)
|
||||||
|
@ -87,8 +111,116 @@ describe 'POST /db/classroom', ->
|
||||||
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||||
expect(res.statusCode).toBe(403)
|
expect(res.statusCode).toBe(403)
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
it 'makes a copy of the list of all levels in all courses', utils.wrap (done) ->
|
||||||
|
teacher = yield utils.initUser({role: 'teacher'})
|
||||||
|
yield utils.loginUser(teacher)
|
||||||
|
data = { name: 'Classroom 2' }
|
||||||
|
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||||
|
classroom = yield Classroom.findById(res.body._id)
|
||||||
|
expect(classroom.get('courses')[0].levels[0].original.toString()).toBe(@levelA.get('original').toString())
|
||||||
|
expect(classroom.get('courses')[0].levels[0].type).toBe('course')
|
||||||
|
expect(classroom.get('courses')[0].levels[0].slug).toBe('level-a')
|
||||||
|
expect(classroom.get('courses')[0].levels[0].name).toBe('Level A')
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe 'GET /db/classroom/:handle/levels', ->
|
||||||
|
|
||||||
|
beforeEach utils.wrap (done) ->
|
||||||
|
yield utils.clearModels [User, Classroom, Course, Level, Campaign]
|
||||||
|
admin = yield utils.initAdmin()
|
||||||
|
yield utils.loginUser(admin)
|
||||||
|
levelJSON = { name: 'King\'s Peak 3', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@level = yield Level.findById(res.body._id)
|
||||||
|
campaignJSON = { name: 'Campaign', levels: {} }
|
||||||
|
paredLevel = _.pick(res.body, 'name', 'original', 'type')
|
||||||
|
campaignJSON.levels[res.body.original] = paredLevel
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSON})
|
||||||
|
@campaign = yield Campaign.findById(res.body._id)
|
||||||
|
@course = Course({name: 'Course', campaignID: @campaign._id})
|
||||||
|
yield @course.save()
|
||||||
|
teacher = yield utils.initUser({role: 'teacher'})
|
||||||
|
yield utils.loginUser(teacher)
|
||||||
|
data = { name: 'Classroom 1' }
|
||||||
|
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||||
|
expect(res.statusCode).toBe(201)
|
||||||
|
@classroom = yield Classroom.findById(res.body._id)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'returns all levels referenced in in the classroom\'s copy of course levels', utils.wrap (done) ->
|
||||||
|
[res, body] = yield request.getAsync { uri: getURL("/db/classroom/#{@classroom.id}/levels"), json: true }
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
levels = res.body
|
||||||
|
expect(levels.length).toBe(1)
|
||||||
|
expect(levels[0].name).toBe("King's Peak 3")
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe 'GET /db/classroom/:handle/levels', ->
|
||||||
|
|
||||||
|
beforeEach utils.wrap (done) ->
|
||||||
|
yield utils.clearModels [User, Classroom, Course, Level, Campaign]
|
||||||
|
admin = yield utils.initAdmin()
|
||||||
|
yield utils.loginUser(admin)
|
||||||
|
|
||||||
|
levelJSON = { name: 'A', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@levelA = yield Level.findById(res.body._id)
|
||||||
|
paredLevelA = _.pick(res.body, 'name', 'original', 'type')
|
||||||
|
|
||||||
|
levelJSON = { name: 'B', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@levelB = yield Level.findById(res.body._id)
|
||||||
|
paredLevelB = _.pick(res.body, 'name', 'original', 'type')
|
||||||
|
|
||||||
|
campaignJSONA = { name: 'Campaign A', levels: {} }
|
||||||
|
campaignJSONA.levels[paredLevelA.original] = paredLevelA
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONA})
|
||||||
|
@campaignA = yield Campaign.findById(res.body._id)
|
||||||
|
|
||||||
|
campaignJSONB = { name: 'Campaign B', levels: {} }
|
||||||
|
campaignJSONB.levels[paredLevelB.original] = paredLevelB
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONB})
|
||||||
|
@campaignB = yield Campaign.findById(res.body._id)
|
||||||
|
|
||||||
|
@courseA = Course({name: 'Course A', campaignID: @campaignA._id})
|
||||||
|
yield @courseA.save()
|
||||||
|
|
||||||
|
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
|
||||||
|
yield @courseB.save()
|
||||||
|
|
||||||
|
teacher = yield utils.initUser({role: 'teacher'})
|
||||||
|
yield utils.loginUser(teacher)
|
||||||
|
data = { name: 'Classroom 1' }
|
||||||
|
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||||
|
expect(res.statusCode).toBe(201)
|
||||||
|
@classroom = yield Classroom.findById(res.body._id)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'returns all levels referenced in in the classroom\'s copy of course levels', utils.wrap (done) ->
|
||||||
|
[res, body] = yield request.getAsync { uri: getURL("/db/classroom/#{@classroom.id}/levels"), json: true }
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
levels = res.body
|
||||||
|
expect(levels.length).toBe(2)
|
||||||
|
|
||||||
|
[res, body] = yield request.getAsync { uri: getURL("/db/classroom/#{@classroom.id}/courses/#{@courseA.id}/levels"), json: true }
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
levels = res.body
|
||||||
|
expect(levels.length).toBe(1)
|
||||||
|
expect(levels[0].original).toBe(@levelA.get('original').toString())
|
||||||
|
|
||||||
|
[res, body] = yield request.getAsync { uri: getURL("/db/classroom/#{@classroom.id}/courses/#{@courseB.id}/levels"), json: true }
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
levels = res.body
|
||||||
|
expect(levels.length).toBe(1)
|
||||||
|
expect(levels[0].original).toBe(@levelB.get('original').toString())
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
||||||
describe 'PUT /db/classroom', ->
|
describe 'PUT /db/classroom', ->
|
||||||
|
|
||||||
it 'clears database users and classrooms', (done) ->
|
it 'clears database users and classrooms', (done) ->
|
||||||
|
|
|
@ -7,6 +7,8 @@ CourseInstance = require '../../../server/models/CourseInstance'
|
||||||
Course = require '../../../server/models/Course'
|
Course = require '../../../server/models/Course'
|
||||||
User = require '../../../server/models/User'
|
User = require '../../../server/models/User'
|
||||||
Classroom = require '../../../server/models/Classroom'
|
Classroom = require '../../../server/models/Classroom'
|
||||||
|
Campaign = require '../../../server/models/Campaign'
|
||||||
|
Level = require '../../../server/models/Level'
|
||||||
Prepaid = require '../../../server/models/Prepaid'
|
Prepaid = require '../../../server/models/Prepaid'
|
||||||
request = require '../request'
|
request = require '../request'
|
||||||
|
|
||||||
|
@ -241,4 +243,124 @@ describe 'DELETE /db/course_instance/:id/members', ->
|
||||||
expect(res.body.members.length).toBe(0)
|
expect(res.body.members.length).toBe(0)
|
||||||
user = yield User.findById(@student.id)
|
user = yield User.findById(@student.id)
|
||||||
expect(_.size(user.get('courseInstances'))).toBe(0)
|
expect(_.size(user.get('courseInstances'))).toBe(0)
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe 'GET /db/course_instance/:handle/levels/:levelOriginal/next', ->
|
||||||
|
|
||||||
|
beforeEach utils.wrap (done) ->
|
||||||
|
yield utils.clearModels [User, Classroom, Course, Level, Campaign]
|
||||||
|
admin = yield utils.initAdmin()
|
||||||
|
yield utils.loginUser(admin)
|
||||||
|
|
||||||
|
levelJSON = { name: 'A', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@levelA = yield Level.findById(res.body._id)
|
||||||
|
paredLevelA = _.pick(res.body, 'name', 'original', 'type')
|
||||||
|
|
||||||
|
levelJSON = { name: 'B', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@levelB = yield Level.findById(res.body._id)
|
||||||
|
paredLevelB = _.pick(res.body, 'name', 'original', 'type')
|
||||||
|
|
||||||
|
levelJSON = { name: 'C', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@levelC = yield Level.findById(res.body._id)
|
||||||
|
paredLevelC = _.pick(res.body, 'name', 'original', 'type')
|
||||||
|
|
||||||
|
campaignJSONA = { name: 'Campaign A', levels: {} }
|
||||||
|
campaignJSONA.levels[paredLevelA.original] = paredLevelA
|
||||||
|
campaignJSONA.levels[paredLevelB.original] = paredLevelB
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONA})
|
||||||
|
@campaignA = yield Campaign.findById(res.body._id)
|
||||||
|
|
||||||
|
campaignJSONB = { name: 'Campaign B', levels: {} }
|
||||||
|
campaignJSONB.levels[paredLevelC.original] = paredLevelC
|
||||||
|
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONB})
|
||||||
|
@campaignB = yield Campaign.findById(res.body._id)
|
||||||
|
|
||||||
|
@courseA = Course({name: 'Course A', campaignID: @campaignA._id})
|
||||||
|
yield @courseA.save()
|
||||||
|
|
||||||
|
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
|
||||||
|
yield @courseB.save()
|
||||||
|
|
||||||
|
teacher = yield utils.initUser({role: 'teacher'})
|
||||||
|
yield utils.loginUser(teacher)
|
||||||
|
data = { name: 'Classroom 1' }
|
||||||
|
classroomsURL = getURL('/db/classroom')
|
||||||
|
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
|
||||||
|
expect(res.statusCode).toBe(201)
|
||||||
|
@classroom = yield Classroom.findById(res.body._id)
|
||||||
|
|
||||||
|
url = getURL('/db/course_instance')
|
||||||
|
|
||||||
|
dataA = { name: 'Some Name', courseID: @courseA.id, classroomID: @classroom.id }
|
||||||
|
[res, body] = yield request.postAsync {uri: url, json: dataA}
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@courseInstanceA = yield CourseInstance.findById(res.body._id)
|
||||||
|
|
||||||
|
dataB = { name: 'Some Other Name', courseID: @courseB.id, classroomID: @classroom.id }
|
||||||
|
[res, body] = yield request.postAsync {uri: url, json: dataB}
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
@courseInstanceB = yield CourseInstance.findById(res.body._id)
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'returns the next level for the course in the linked classroom', utils.wrap (done) ->
|
||||||
|
[res, body] = yield request.getAsync { uri: utils.getURL("/db/course_instance/#{@courseInstanceA.id}/levels/#{@levelA.id}/next"), json: true }
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
expect(res.body.original).toBe(@levelB.original.toString())
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'returns 404 if the given level is the last level in its course', utils.wrap (done) ->
|
||||||
|
[res, body] = yield request.getAsync { uri: utils.getURL("/db/course_instance/#{@courseInstanceA.id}/levels/#{@levelB.id}/next"), json: true }
|
||||||
|
expect(res.statusCode).toBe(404)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'returns 404 if the given level is not in the course instance\'s course', utils.wrap (done) ->
|
||||||
|
[res, body] = yield request.getAsync { uri: utils.getURL("/db/course_instance/#{@courseInstanceB.id}/levels/#{@levelA.id}/next"), json: true }
|
||||||
|
expect(res.statusCode).toBe(404)
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
||||||
|
describe 'GET /db/course_instance/:handle/classroom', ->
|
||||||
|
|
||||||
|
beforeEach utils.wrap (done) ->
|
||||||
|
yield utils.clearModels [User, CourseInstance, Classroom]
|
||||||
|
@owner = yield utils.initUser()
|
||||||
|
yield @owner.save()
|
||||||
|
@member = yield utils.initUser()
|
||||||
|
yield @member.save()
|
||||||
|
@classroom = new Classroom({
|
||||||
|
ownerID: @owner._id
|
||||||
|
members: [@member._id]
|
||||||
|
})
|
||||||
|
yield @classroom.save()
|
||||||
|
@courseInstance = new CourseInstance({classroomID: @classroom._id})
|
||||||
|
yield @courseInstance.save()
|
||||||
|
@url = getURL("/db/course_instance/#{@courseInstance.id}/classroom")
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'returns the course instance\'s referenced classroom', utils.wrap (done) ->
|
||||||
|
yield utils.loginUser @owner
|
||||||
|
[res, body] = yield request.getAsync(@url, {json: true})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
expect(body.code).toBeDefined()
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'works if you are the owner or member', utils.wrap (done) ->
|
||||||
|
yield utils.loginUser @member
|
||||||
|
[res, body] = yield request.getAsync(@url, {json: true})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
expect(body.code).toBeUndefined()
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'does not work if you are not the owner or a member', utils.wrap (done) ->
|
||||||
|
@user = yield utils.initUser()
|
||||||
|
yield utils.loginUser @user
|
||||||
|
[res, body] = yield request.getAsync(@url, {json: true})
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
done()
|
done()
|
|
@ -13,5 +13,36 @@ module.exports = new Classroom(
|
||||||
ownerID: "teacher0",
|
ownerID: "teacher0",
|
||||||
aceConfig:
|
aceConfig:
|
||||||
language: 'python'
|
language: 'python'
|
||||||
|
courses: [
|
||||||
|
{
|
||||||
|
_id: "course0",
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
original: 'level0_0'
|
||||||
|
name: 'level0_0'
|
||||||
|
type: 'hero'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
original: 'level0_1'
|
||||||
|
name: 'level0_1'
|
||||||
|
type: 'hero'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
original: 'level0_2'
|
||||||
|
name: 'level0_2'
|
||||||
|
type: 'hero'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
original: 'level0_3'
|
||||||
|
name: 'level0_3'
|
||||||
|
type: 'hero'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "course1",
|
||||||
|
levels: []
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe 'CoursesHelper', ->
|
||||||
|
|
||||||
describe 'progressData.get({classroom, course})', ->
|
describe 'progressData.get({classroom, course})', ->
|
||||||
it 'returns object with .completed=true and .started=true', ->
|
it 'returns object with .completed=true and .started=true', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
progress = progressData.get {@classroom, @course}
|
progress = progressData.get {@classroom, @course}
|
||||||
expect(progress.completed).toBe true
|
expect(progress.completed).toBe true
|
||||||
expect(progress.started).toBe true
|
expect(progress.started).toBe true
|
||||||
|
@ -35,14 +35,14 @@ describe 'CoursesHelper', ->
|
||||||
describe 'progressData.get({classroom, course, level, user})', ->
|
describe 'progressData.get({classroom, course, level, user})', ->
|
||||||
it 'returns object with .completed=true and .started=true', ->
|
it 'returns object with .completed=true and .started=true', ->
|
||||||
for student in @students.models
|
for student in @students.models
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
progress = progressData.get {@classroom, @course, user: student}
|
progress = progressData.get {@classroom, @course, user: student}
|
||||||
expect(progress.completed).toBe true
|
expect(progress.completed).toBe true
|
||||||
expect(progress.started).toBe true
|
expect(progress.started).toBe true
|
||||||
|
|
||||||
describe 'progressData.get({classroom, course, level, user})', ->
|
describe 'progressData.get({classroom, course, level, user})', ->
|
||||||
it 'returns object with .completed=true and .started=true', ->
|
it 'returns object with .completed=true and .started=true', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
for level in @campaign.getLevels().models
|
for level in @campaign.getLevels().models
|
||||||
progress = progressData.get {@classroom, @course, level}
|
progress = progressData.get {@classroom, @course, level}
|
||||||
expect(progress.completed).toBe true
|
expect(progress.completed).toBe true
|
||||||
|
@ -50,7 +50,7 @@ describe 'CoursesHelper', ->
|
||||||
|
|
||||||
describe 'progressData.get({classroom, course, level, user})', ->
|
describe 'progressData.get({classroom, course, level, user})', ->
|
||||||
it 'returns object with .completed=true and .started=true', ->
|
it 'returns object with .completed=true and .started=true', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
for level in @campaign.getLevels().models
|
for level in @campaign.getLevels().models
|
||||||
for user in @students.models
|
for user in @students.models
|
||||||
progress = progressData.get {@classroom, @course, level, user}
|
progress = progressData.get {@classroom, @course, level, user}
|
||||||
|
@ -64,20 +64,20 @@ describe 'CoursesHelper', ->
|
||||||
@courseInstances = require 'test/app/fixtures/course-instances'
|
@courseInstances = require 'test/app/fixtures/course-instances'
|
||||||
|
|
||||||
it 'progressData.get({classroom, course}) returns object with .completed=false', ->
|
it 'progressData.get({classroom, course}) returns object with .completed=false', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
progress = progressData.get {@classroom, @course}
|
progress = progressData.get {@classroom, @course}
|
||||||
expect(progress.completed).toBe false
|
expect(progress.completed).toBe false
|
||||||
|
|
||||||
describe 'when NOT all students have completed a level', ->
|
describe 'when NOT all students have completed a level', ->
|
||||||
it 'progressData.get({classroom, course, level}) returns object with .completed=false and .started=true', ->
|
it 'progressData.get({classroom, course, level}) returns object with .completed=false and .started=true', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
for level in @campaign.getLevels().models
|
for level in @campaign.getLevels().models
|
||||||
progress = progressData.get {@classroom, @course, level}
|
progress = progressData.get {@classroom, @course, level}
|
||||||
expect(progress.completed).toBe false
|
expect(progress.completed).toBe false
|
||||||
|
|
||||||
describe 'when the student has completed the course', ->
|
describe 'when the student has completed the course', ->
|
||||||
it 'progressData.get({classroom, course, user}) returns object with .completed=true and .started=true', ->
|
it 'progressData.get({classroom, course, user}) returns object with .completed=true and .started=true', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
student = @students.get('student0')
|
student = @students.get('student0')
|
||||||
progress = progressData.get {@classroom, @course, user: student}
|
progress = progressData.get {@classroom, @course, user: student}
|
||||||
expect(progress.completed).toBe true
|
expect(progress.completed).toBe true
|
||||||
|
@ -85,7 +85,7 @@ describe 'CoursesHelper', ->
|
||||||
|
|
||||||
describe 'when the student has NOT completed the course', ->
|
describe 'when the student has NOT completed the course', ->
|
||||||
it 'progressData.get({classroom, course, user}) returns object with .completed=false and .started=true', ->
|
it 'progressData.get({classroom, course, user}) returns object with .completed=false and .started=true', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
student = @students.get('student1')
|
student = @students.get('student1')
|
||||||
progress = progressData.get {@classroom, @course, user: student}
|
progress = progressData.get {@classroom, @course, user: student}
|
||||||
expect(progress.completed).toBe false
|
expect(progress.completed).toBe false
|
||||||
|
@ -93,7 +93,7 @@ describe 'CoursesHelper', ->
|
||||||
|
|
||||||
describe 'when the student has completed the level', ->
|
describe 'when the student has completed the level', ->
|
||||||
it 'progressData.get({classroom, course, level, user}) returns object with .completed=true and .started=true', ->
|
it 'progressData.get({classroom, course, level, user}) returns object with .completed=true and .started=true', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
student = @students.get('student0')
|
student = @students.get('student0')
|
||||||
for level in @campaign.getLevels().models
|
for level in @campaign.getLevels().models
|
||||||
progress = progressData.get {@classroom, @course, level, user: student}
|
progress = progressData.get {@classroom, @course, level, user: student}
|
||||||
|
@ -102,7 +102,7 @@ describe 'CoursesHelper', ->
|
||||||
|
|
||||||
describe 'when the student has NOT completed the level but has started', ->
|
describe 'when the student has NOT completed the level but has started', ->
|
||||||
it 'progressData.get({classroom, course, level, user}) returns object with .completed=true and .started=true', ->
|
it 'progressData.get({classroom, course, level, user}) returns object with .completed=true and .started=true', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
user = @students.get('student2')
|
user = @students.get('student2')
|
||||||
level = @campaign.getLevels().get('level0_0')
|
level = @campaign.getLevels().get('level0_0')
|
||||||
progress = progressData.get {@classroom, @course, level, user}
|
progress = progressData.get {@classroom, @course, level, user}
|
||||||
|
@ -111,7 +111,7 @@ describe 'CoursesHelper', ->
|
||||||
|
|
||||||
describe 'when the student has NOT started the level', ->
|
describe 'when the student has NOT started the level', ->
|
||||||
it 'progressData.get({classroom, course, level, user}) returns object with .completed=false and .started=false', ->
|
it 'progressData.get({classroom, course, level, user}) returns object with .completed=false and .started=false', ->
|
||||||
progressData = helper.calculateAllProgress(@classrooms, @courses, @campaigns, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students)
|
||||||
user = @students.get('student3')
|
user = @students.get('student3')
|
||||||
level = @campaign.getLevels().get('level0_0')
|
level = @campaign.getLevels().get('level0_0')
|
||||||
progress = progressData.get {@classroom, @course, level, user}
|
progress = progressData.get {@classroom, @course, level, user}
|
||||||
|
|
|
@ -24,6 +24,8 @@ describe 'CourseVictoryModal', ->
|
||||||
courseInstanceID: '56414c3868785b5f152424f1'
|
courseInstanceID: '56414c3868785b5f152424f1'
|
||||||
courseID: '560f1a9f22961295f9427742'
|
courseID: '560f1a9f22961295f9427742'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextLevelRequest = null
|
||||||
|
|
||||||
handleRequests = ->
|
handleRequests = ->
|
||||||
requests = jasmine.Ajax.requests.all()
|
requests = jasmine.Ajax.requests.all()
|
||||||
|
@ -33,12 +35,14 @@ describe 'CourseVictoryModal', ->
|
||||||
earnedAchievementRequests = _.where(requests, {url: '/db/earned_achievement'})
|
earnedAchievementRequests = _.where(requests, {url: '/db/earned_achievement'})
|
||||||
for [request, response] in _.zip(earnedAchievementRequests, fixtures.earnedAchievements)
|
for [request, response] in _.zip(earnedAchievementRequests, fixtures.earnedAchievements)
|
||||||
request.respondWith({status: 200, responseText: JSON.stringify(response)})
|
request.respondWith({status: 200, responseText: JSON.stringify(response)})
|
||||||
|
|
||||||
sessionsRequest = _.find(requests, (r) -> _.string.startsWith(r.url, '/db/course_instance'))
|
sessionsRequest = _.findWhere(requests, {url: '/db/course_instance/56414c3868785b5f152424f1/my-course-level-sessions'})
|
||||||
sessionsRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.courseInstanceSessions)})
|
sessionsRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.courseInstanceSessions)})
|
||||||
|
|
||||||
campaignRequest = _.findWhere(requests, {url: '/db/campaign/55b29efd1cd6abe8ce07db0d'})
|
classroomRequest = _.findWhere(requests, {url: '/db/course_instance/56414c3868785b5f152424f1/classroom'})
|
||||||
campaignRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.campaign)})
|
classroomRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.campaign)}) # TODO: Fix this...
|
||||||
|
|
||||||
|
nextLevelRequest = _.findWhere(requests, {url: '/db/course_instance/56414c3868785b5f152424f1/levels/54173c90844506ae0195a0b4/next'})
|
||||||
|
|
||||||
describe 'given a course level with a next level and no item or hero rewards', ->
|
describe 'given a course level with a next level and no item or hero rewards', ->
|
||||||
modal = null
|
modal = null
|
||||||
|
@ -47,6 +51,7 @@ describe 'CourseVictoryModal', ->
|
||||||
options = makeViewOptions()
|
options = makeViewOptions()
|
||||||
modal = new CourseVictoryModal(options)
|
modal = new CourseVictoryModal(options)
|
||||||
handleRequests()
|
handleRequests()
|
||||||
|
nextLevelRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.nextLevel)})
|
||||||
_.defer done
|
_.defer done
|
||||||
|
|
||||||
it 'only shows the ProgressView', ->
|
it 'only shows the ProgressView', ->
|
||||||
|
@ -80,6 +85,7 @@ describe 'CourseVictoryModal', ->
|
||||||
delete options.nextLevel
|
delete options.nextLevel
|
||||||
modal = new CourseVictoryModal(options)
|
modal = new CourseVictoryModal(options)
|
||||||
handleRequests()
|
handleRequests()
|
||||||
|
nextLevelRequest.respondWith({status: 404, responseText: '{}'})
|
||||||
_.defer done
|
_.defer done
|
||||||
|
|
||||||
describe 'its ProgressView', ->
|
describe 'its ProgressView', ->
|
||||||
|
@ -112,6 +118,7 @@ describe 'CourseVictoryModal', ->
|
||||||
|
|
||||||
modal = new CourseVictoryModal(options)
|
modal = new CourseVictoryModal(options)
|
||||||
handleRequests()
|
handleRequests()
|
||||||
|
nextLevelRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.nextLevel)})
|
||||||
_.defer done
|
_.defer done
|
||||||
|
|
||||||
it 'includes a NewItemView when the level rewards a new item', ->
|
it 'includes a NewItemView when the level rewards a new item', ->
|
||||||
|
|
|
@ -10,8 +10,6 @@ describe 'TeacherClassView', ->
|
||||||
# it 'responds with 401 error'
|
# it 'responds with 401 error'
|
||||||
# it 'shows Log In and Create Account buttons'
|
# it 'shows Log In and Create Account buttons'
|
||||||
|
|
||||||
@view = null
|
|
||||||
|
|
||||||
# describe "when you don't own the class", ->
|
# describe "when you don't own the class", ->
|
||||||
# it 'responds with 403 error'
|
# it 'responds with 403 error'
|
||||||
# it 'shows Log Out button'
|
# it 'shows Log Out button'
|
||||||
|
@ -22,14 +20,12 @@ describe 'TeacherClassView', ->
|
||||||
@classroom = require 'test/app/fixtures/classrooms/active-classroom'
|
@classroom = require 'test/app/fixtures/classrooms/active-classroom'
|
||||||
@students = require 'test/app/fixtures/students'
|
@students = require 'test/app/fixtures/students'
|
||||||
@courses = require 'test/app/fixtures/courses'
|
@courses = require 'test/app/fixtures/courses'
|
||||||
@campaigns = require 'test/app/fixtures/campaigns'
|
|
||||||
@courseInstances = require 'test/app/fixtures/course-instances'
|
@courseInstances = require 'test/app/fixtures/course-instances'
|
||||||
@levelSessions = require 'test/app/fixtures/level-sessions-partially-completed'
|
@levelSessions = require 'test/app/fixtures/level-sessions-partially-completed'
|
||||||
|
|
||||||
@view = new TeacherClassView()
|
@view = new TeacherClassView()
|
||||||
@view.classroom.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@classroom) })
|
@view.classroom.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@classroom) })
|
||||||
@view.courses.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@courses) })
|
@view.courses.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@courses) })
|
||||||
@view.campaigns.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@campaigns) })
|
|
||||||
@view.courseInstances.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@courseInstances) })
|
@view.courseInstances.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@courseInstances) })
|
||||||
@view.students.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@students) })
|
@view.students.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@students) })
|
||||||
@view.classroom.sessions.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@levelSessions) })
|
@view.classroom.sessions.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@levelSessions) })
|
||||||
|
|
Loading…
Reference in a new issue