Merge branch 'master' into production

This commit is contained in:
phoenixeliot 2016-07-25 12:08:45 -07:00
commit 2447e790e1
13 changed files with 111 additions and 42 deletions

View file

@ -4,3 +4,8 @@ CocoCollection = require 'collections/CocoCollection'
module.exports = class Courses extends CocoCollection
model: Course
url: '/db/course'
fetchReleased: (options = {}) ->
options.data ?= {}
options.data.releasePhase = 'released'
@fetch(options)

View file

@ -11,7 +11,8 @@ _.extend CourseSchema.properties,
pricePerSeat: {type: 'number', description: 'Price per seat in USD cents.'} # deprecated
free: { type: 'boolean' }
screenshot: c.url {title: 'URL', description: 'Link to course screenshot.'}
adminOnly: {type: 'boolean', description: 'Whether the course is in admin-only testing mode still and will not show up for normal users.'}
adminOnly: { type: 'boolean', description: 'Deprecated in favor of releasePhase.' }
releasePhase: { type: {enum: ['beta', 'released'] }, description: "How far along the course's development is, determining who sees it." }
c.extendBasicProperties CourseSchema, 'Course'

View file

@ -3,7 +3,7 @@ template = require 'templates/new-home-view'
CocoCollection = require 'collections/CocoCollection'
TrialRequest = require 'models/TrialRequest'
TrialRequests = require 'collections/TrialRequests'
Course = require 'models/Course'
Courses = require 'collections/Courses'
utils = require 'core/utils'
storage = require 'core/storage'
{logoutUser, me} = require('core/auth')
@ -36,8 +36,8 @@ module.exports = class NewHomeView extends RootView
'esc': 'onEscapePressed'
initialize: (options) ->
@courses = new CocoCollection [], {url: "/db/course", model: Course}
@supermodel.loadCollection(@courses, 'courses')
@courses = new Courses()
@supermodel.trackRequest @courses.fetchReleased()
if me.isTeacher()
@trialRequests = new TrialRequests()

View file

@ -1,7 +1,7 @@
app = require 'core/application'
CocoCollection = require 'collections/CocoCollection'
CocoModel = require 'models/CocoModel'
Course = require 'models/Course'
Courses = require 'collections/Courses'
Campaigns = require 'collections/Campaigns'
Classroom = require 'models/Classroom'
Classrooms = require 'collections/Classrooms'
@ -40,8 +40,8 @@ module.exports = class TeacherCoursesView extends RootView
@ownedClassrooms = new Classrooms()
@ownedClassrooms.fetchMine({data: {project: '_id'}})
@supermodel.trackCollection(@ownedClassrooms)
@courses = new CocoCollection([], { url: "/db/course", model: Course})
@supermodel.loadCollection(@courses, 'courses')
@courses = new Courses()
@supermodel.trackRequest @courses.fetchReleased()
@campaigns = new Campaigns()
@supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } })
@

View file

@ -2,6 +2,7 @@
// Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// eg: mongo localhost:27017/coco scripts/mongodb/updateCourses.js
// NOTE: uses name as unique identifier, so changing the name will insert a new course
// NOTE: pricePerSeat in USD cents
@ -15,7 +16,8 @@ var courses =
description: "Learn basic syntax, while loops, and the CodeCombat environment.",
duration: NumberInt(1),
free: true,
screenshot: "/images/pages/courses/101_info.png"
screenshot: "/images/pages/courses/101_info.png",
releasePhase: 'released'
},
{
name: "Computer Science 2",
@ -24,7 +26,8 @@ var courses =
description: "Introduce Arguments, Variables, If Statements, and Arithmetic.",
duration: NumberInt(5),
free: false,
screenshot: "/images/pages/courses/102_info.png"
screenshot: "/images/pages/courses/102_info.png",
releasePhase: 'released'
},
{
name: "Computer Science 3",
@ -33,7 +36,8 @@ var courses =
description: "Introduces arithmetic, counters, advanced while loops, break, continue, arrays.",
duration: NumberInt(5),
free: false,
screenshot: "/images/pages/courses/103_info.png"
screenshot: "/images/pages/courses/103_info.png",
releasePhase: 'released'
},
{
name: "Computer Science 4",
@ -42,7 +46,8 @@ var courses =
description: "Introduces object literals, for loops, function definitions, drawing, and modulo.",
duration: NumberInt(5),
free: false,
screenshot: "/images/pages/courses/104_info.png"
screenshot: "/images/pages/courses/104_info.png",
releasePhase: 'released'
},
{
name: "Computer Science 5",
@ -51,7 +56,8 @@ var courses =
description: "Introduces function parameters, function return values and algorithms.",
duration: NumberInt(5),
free: false,
screenshot: "/images/pages/courses/105_info.png"
screenshot: "/images/pages/courses/105_info.png",
releasePhase: 'released'
},
{
name: "CS: Game Development 1",
@ -61,7 +67,8 @@ var courses =
duration: NumberInt(5),
free: false,
//screenshot: "/images/pages/courses/105_info.png",
adminOnly: true
adminOnly: true, // Until we finish transitioning to releasePhase
releasePhase: 'beta'
},
{
name: "CS: Web Development 1",
@ -71,7 +78,8 @@ var courses =
duration: NumberInt(5),
free: false,
//screenshot: "/images/pages/courses/105_info.png",
adminOnly: true
adminOnly: true, // Until we finish transitioning to releasePhase
releasePhase: 'beta'
},
{
name: "CS: Web Development 2",
@ -81,7 +89,8 @@ var courses =
duration: NumberInt(5),
free: false,
//screenshot: "/images/pages/courses/105_info.png",
adminOnly: true
adminOnly: true, // Until we finish transitioning to releasePhase
releasePhase: 'beta'
}
];

View file

@ -167,7 +167,7 @@ module.exports =
generateCoursesData: co.wrap (req) ->
# helper function for generating the latest version of courses
query = {}
query = {adminOnly: {$ne: true}} unless req.user?.isAdmin()
query = {releasePhase: 'released'} unless req.user?.isAdmin()
courses = yield Course.find(query)
courses = Course.sortCourses courses
campaigns = yield Campaign.find({_id: {$in: (course.get('campaignID') for course in courses)}})

View file

@ -51,9 +51,9 @@ module.exports =
res.status(200).send(level)
get: (Model, options={}) -> wrap (req, res) ->
# Don't use standard rest middleware get, because we want to filter out adminOnly courses for non-admins
query = {}
query = {adminOnly: {$ne: true}} unless req.user?.isAdmin()
if req.query.releasePhase
query.releasePhase = req.query.releasePhase
dbq = Model.find(query)
dbq.select(parse.getProjectFromReq(req))
results = yield database.viewSearch(dbq, req)

View file

@ -90,6 +90,7 @@ describe 'POST /db/classroom', ->
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSONC})
expect(res.statusCode).toBe(200)
@levelC = yield Level.findById(res.body._id)
campaignJSON = { name: 'Campaign', levels: {} }
paredLevelC = _.pick(@levelC.toObject(), 'name', 'original', 'type', 'slug', 'practice')
paredLevelC.campaignIndex = 2
@ -100,9 +101,10 @@ describe 'POST /db/classroom', ->
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})
@course = Course({name: 'Course', campaignID: @campaign._id, releasePhase: 'released'})
yield @course.save()
done()
@ -144,6 +146,57 @@ describe 'POST /db/classroom', ->
expect(classroom.get('courses')[0].levels[0].name).toBe('Level A')
done()
describe 'when there are unreleased courses', ->
beforeEach utils.wrap (done) ->
admin = yield utils.initAdmin()
yield utils.loginUser(admin)
betaLevelJSON = { name: 'Beta Level', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: betaLevelJSON})
expect(res.statusCode).toBe(200)
@betaLevel = yield Level.findById(res.body._id)
betaCampaignJSON = { name: 'Beta Campaign', levels: {} }
paredBetaLevel = _.pick(@betaLevel.toObject(), 'name', 'original', 'type', 'slug')
paredBetaLevel.campaignIndex = 0
betaCampaignJSON.levels[@betaLevel.get('original').toString()] = paredBetaLevel
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: betaCampaignJSON})
@betaCampaign = yield Campaign.findById(res.body._id)
@betaCourse = Course({name: 'Beta Course', campaignID: @betaCampaign._id, releasePhase: 'beta'})
yield @betaCourse.save()
done()
it 'includes unreleased courses for admin teachers', utils.wrap (done) ->
adminTeacher = yield utils.initUser({ role: 'teacher', permissions: ['admin'] })
yield utils.loginUser(adminTeacher)
data = { name: 'Classroom 3' }
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
classroom = yield Classroom.findById(res.body._id)
expect(classroom.get('courses').length).toBe(2)
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')
expect(classroom.get('courses')[1].levels[0].original.toString()).toBe(@betaLevel.get('original').toString())
expect(classroom.get('courses')[1].levels[0].type).toBe('course')
expect(classroom.get('courses')[1].levels[0].slug).toBe('beta-level')
expect(classroom.get('courses')[1].levels[0].name).toBe('Beta Level')
done()
it 'does not include unreleased courses for non-admin teachers', utils.wrap (done) ->
teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(teacher)
data = { name: 'Classroom 4' }
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
classroom = yield Classroom.findById(res.body._id)
expect(classroom.get('courses').length).toBe(1)
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) ->
@ -159,7 +212,7 @@ describe 'GET /db/classroom/:handle/levels', ->
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})
@course = Course({name: 'Course', campaignID: @campaign._id, releasePhase: 'released'})
yield @course.save()
teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(teacher)
@ -206,10 +259,10 @@ describe 'GET /db/classroom/:handle/levels', ->
[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})
@courseA = Course({name: 'Course A', campaignID: @campaignA._id, releasePhase: 'released'})
yield @courseA.save()
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
@courseB = Course({name: 'Course B', campaignID: @campaignB._id, releasePhase: 'released'})
yield @courseB.save()
teacher = yield utils.initUser({role: 'teacher'})
@ -286,7 +339,7 @@ describe 'POST /db/classroom/-/members', ->
yield utils.clearModels([User, Classroom, Course, Campaign])
@campaign = new Campaign({levels: {}})
yield @campaign.save()
@course = new Course({free: true, campaignID: @campaign._id})
@course = new Course({free: true, campaignID: @campaign._id, releasePhase: 'released'})
yield @course.save()
@teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(@teacher)
@ -560,7 +613,7 @@ describe 'GET /db/classroom/:handle/update-courses', ->
teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(admin)
yield utils.makeCourse({}, {campaign: yield utils.makeCampaign()})
yield utils.makeCourse({releasePhase: 'released'}, {campaign: yield utils.makeCampaign()})
yield utils.loginUser(teacher)
data = { name: 'Classroom 2' }
@ -569,7 +622,7 @@ describe 'GET /db/classroom/:handle/update-courses', ->
expect(classroom.get('courses').length).toBe(1)
yield utils.loginUser(admin)
yield utils.makeCourse({}, {campaign: yield utils.makeCampaign()})
yield utils.makeCourse({releasePhase: 'released'}, {campaign: yield utils.makeCampaign()})
classroom = yield Classroom.findById(res.body._id)
expect(classroom.get('courses').length).toBe(1)
@ -580,5 +633,4 @@ describe 'GET /db/classroom/:handle/update-courses', ->
classroom = yield Classroom.findById(res.body._id)
expect(classroom.get('courses').length).toBe(2)
done()
done()

View file

@ -109,7 +109,7 @@ describe 'POST /db/course_instance/:id/members', ->
yield utils.loginUser(admin)
@level = yield utils.makeLevel({type: 'course'})
@campaign = yield utils.makeCampaign({}, {levels: [@level]})
@course = yield utils.makeCourse({free: true}, {campaign: @campaign})
@course = yield utils.makeCourse({free: true, releasePhase: 'released'}, {campaign: @campaign})
@student = yield utils.initUser({role: 'student'})
@prepaid = yield utils.makePrepaid({creator: @teacher.id})
members = [@student]
@ -291,10 +291,10 @@ describe 'GET /db/course_instance/:handle/levels/:levelOriginal/next', ->
[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})
@courseA = Course({name: 'Course A', campaignID: @campaignA._id, releasePhase: 'released'})
yield @courseA.save()
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
@courseB = Course({name: 'Course B', campaignID: @campaignB._id, releasePhase: 'released'})
yield @courseB.save()
yield utils.loginUser(teacher)
@ -379,7 +379,7 @@ describe 'GET /db/course_instance/:handle/course', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels [User, CourseInstance, Classroom]
@course = new Course({})
@course = new Course({ releasePhase: 'released' })
yield @course.save()
@courseInstance = new CourseInstance({courseID: @course._id})
yield @courseInstance.save()
@ -404,7 +404,7 @@ describe 'POST /db/course_instance/-/recent', ->
@admin = yield utils.initAdmin()
yield utils.loginUser(@admin)
@campaign = yield utils.makeCampaign()
@course = yield utils.makeCourse({free: true}, {campaign: @campaign})
@course = yield utils.makeCourse({free: true, releasePhase: 'released'}, {campaign: @campaign})
@student = yield utils.initUser({role: 'student'})
@prepaid = yield utils.makePrepaid({creator: @teacher.id})
members = [@student]

View file

@ -21,8 +21,8 @@ courseFixture = {
describe 'GET /db/course', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels([Course, User])
yield new Course({ name: 'Course 1' }).save()
yield new Course({ name: 'Course 2' }).save()
yield new Course({ name: 'Course 1', releasePhase: 'released' }).save()
yield new Course({ name: 'Course 2', releasePhase: 'released' }).save()
yield utils.becomeAnonymous()
done()
@ -36,7 +36,7 @@ describe 'GET /db/course/:handle', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels([Course, User])
@course = yield new Course({ name: 'Some Name' }).save()
@course = yield new Course({ name: 'Some Name', releasePhase: 'released' }).save()
yield utils.becomeAnonymous()
done()
@ -96,10 +96,10 @@ describe 'GET /db/course/:handle/levels/:levelOriginal/next', ->
[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})
@courseA = Course({name: 'Course A', campaignID: @campaignA._id, releasePhase: 'released'})
yield @courseA.save()
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
@courseB = Course({name: 'Course B', campaignID: @campaignB._id, releasePhase: 'released'})
yield @courseB.save()
teacher = yield utils.initUser({role: 'teacher'})

View file

@ -67,11 +67,11 @@ describe 'GET /db/level/:handle/session', ->
@level = yield utils.makeLevel({type: 'course'})
# To ensure test compares original, not id, make them different. TODO: Make factories do this normally?
@level.set('original', new mongoose.Types.ObjectId())
@level.set('original', new mongoose.Types.ObjectId())
@level.save()
@campaign = yield utils.makeCampaign({}, {levels: [@level]})
@course = yield utils.makeCourse({free: true}, {campaign: @campaign})
@course = yield utils.makeCourse({free: true, releasePhase: 'released'}, {campaign: @campaign})
@student = yield utils.initUser({role: 'student'})
members = [@student]
teacher = yield utils.initUser({role: 'teacher'})
@ -125,7 +125,7 @@ describe 'GET /db/level/:handle/session', ->
it 'creates the session if the user is enrolled', utils.wrap (done) ->
@student.set({
coursePrepaid: {
coursePrepaid: {
_id: {}
startDate: moment().subtract(1, 'month').toISOString()
endDate: moment().add(1, 'month').toISOString()

View file

@ -140,6 +140,8 @@ module.exports = mw =
if sources.campaign and not data.campaignID
data.campaignID = sources.campaign._id
data.releasePhase ||= 'released'
course = new Course(data)
return course.save()

View file

@ -18,6 +18,7 @@ module.exports = {
attrs = _.extend({}, {
_id: _id
name: _.string.humanize(_id)
releasePhase: 'released'
}, attrs)
attrs.campaignID ?= sources.campaign?.id or _.uniqueId('campaign_')
@ -185,6 +186,5 @@ module.exports = {
organization: 'Greendale'
}
}, attrs)
}
}