Update teacher surveys

Removing trial subscriptions
Updating trial page copy
Adding special HoC trial
Introducing a new course prepaid property endDate, which won’t be
returned or redeemed after the date.
This commit is contained in:
Matt Lott 2015-11-24 10:32:15 -08:00
parent ec7f17a149
commit d7a66722b9
11 changed files with 183 additions and 79 deletions

View file

@ -664,16 +664,20 @@
title: "Teacher Survey"
must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
retrieving: "Retrieving information..."
being_reviewed_1: "Your application for a free trial subscription is being"
being_reviewed_1: "Your application for a free trial is being" # {change}
being_reviewed_2: "reviewed."
approved_1: "Your application for a free trial subscription was"
approved_1: "Your application for a free trial was" # {change}
approved_2: "approved."
approved_3: "Further instructions have been sent to"
denied_1: "Your application for a free trial subscription has been"
approved_4: "Enroll your students on the"
approved_5: "courses"
approved_6: "page."
denied_1: "Your application for a free trial has been" # {change}
denied_2: "denied."
contact_1: "Please contact"
contact_2: "if you have further questions."
description_1: "We offer free subscriptions to teachers for evaluation purposes. You can find more information on our"
description_1: "We offer free trials to teachers. You will be given 2 free enrollments which can be used to enroll students in paid courses." # {change}
description_1b: "You can find more information on our"
description_2: "teachers"
description_3: "page."
description_4: "Please fill out this quick survey and well email you setup instructions."

View file

@ -6,13 +6,14 @@ module.exports = class Prepaid extends CocoModel
urlRoot: '/db/prepaid'
openSpots: ->
@get('maxRedeemers') - @get('redeemers')?.length
return @get('maxRedeemers') - @get('redeemers')?.length if @get('redeemers')?
@get('maxRedeemers')
userHasRedeemed: (userID) ->
for redeemer in @get('redeemers')
return redeemer.date if redeemer.userID is userID
return null
initialize: ->
@listenTo @, 'add', ->
maxRedeemers = @get('maxRedeemers')

View file

@ -1,8 +1,5 @@
#teachers-free-trial-view
.input-email-address
width: 40%
.input-school
width: 40%
@ -18,6 +15,9 @@
.thanks-submit
display: none
.email-address
margin-right: 12px
.error-message
display: none
color: red

View file

@ -16,10 +16,14 @@ block content
strong(data-i18n="teachers_survey.being_reviewed_2")
else if existingRequest.get('status') === 'approved'
p
span.spr(data-i18n="teachers_survey.approved_1")
strong.spr(data-i18n="teachers_survey.approved_2")
span.spr(data-i18n="teachers_survey.approved_3")
strong= existingRequest.get('properties').email
span.spr(data-i18n="teachers_survey.approved_1")
strong.spr(data-i18n="teachers_survey.approved_2")
span.spr(data-i18n="teachers_survey.approved_3")
strong= existingRequest.get('properties').email
p
span.spr(data-i18n="teachers_survey.approved_4")
a(href='/courses', data-i18n="teachers_survey.approved_5")
span.spl(data-i18n="teachers_survey.approved_6")
else
p
span.spr(data-i18n="teachers_survey.denied_1")
@ -29,15 +33,20 @@ block content
a(href='mailto:team@codecombat.com') team@codecombat.com
span.spl(data-i18n="teachers_survey.contact_2")
else
p(data-i18n="teachers_survey.description_1")
p
strong.spr Hour of Code Special!
span Complete the survey by December 31st and enroll all your students in the paid courses for 2 months.
p
span.spr(data-i18n="teachers_survey.description_1")
span.spr(data-i18n="teachers_survey.description_1b")
a(href='/teachers', data-i18n="teachers_survey.description_2")
span.spl(data-i18n="teachers_survey.description_3")
p(data-i18n="teachers_survey.description_4")
p.container-email-address
label.control-label(data-i18n="teachers_survey.email")
br
input.control-label.input-email-address(type='text', value=view.email)
span.email-address= view.email
a(href='/account/settings') Change
p.container-school
label.control-label(data-i18n="teachers_survey.school")
br

View file

@ -30,7 +30,6 @@ module.exports = class TeachersFreeTrialView extends RootView
$('.radio-other').prop("checked", true)
onClickSubmit: (e) ->
email = $('.input-email-address').val()
school = $('.input-school').val()
location = $('.input-location').val()
age = $('input[name=age]:checked').val()
@ -46,10 +45,6 @@ module.exports = class TeachersFreeTrialView extends RootView
$('.container-num-students').removeClass('has-error')
$('.container-heard-about').removeClass('has-error')
$('.error-message').hide()
unless email
$('.container-email-address').addClass('has-error')
$('.error-message').show()
return
unless school
$('.container-school').addClass('has-error')
$('.error-message').show()
@ -75,7 +70,7 @@ module.exports = class TeachersFreeTrialView extends RootView
trialRequest = new TrialRequest
type: 'subscription'
properties:
email: email
email: @email
school: school
location: location
age: age

View file

@ -161,7 +161,9 @@ module.exports = class TeacherCoursesView extends RootView
return
user = @usersToRedeem.first()
prepaid = @prepaids.find (prepaid) -> prepaid.openSpots()
prepaid = @prepaids.find((prepaid) -> prepaid.get('properties').endDate? and prepaid.openSpots())
prepaid = @prepaids.find((prepaid) -> prepaid.openSpots()) unless prepaid
$.ajax({
method: 'POST'
url: _.result(prepaid, 'url') + '/redeemers'

View file

@ -74,8 +74,9 @@ PrepaidHandler = class PrepaidHandler extends Handler
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) if not prepaid
return @sendForbiddenError(res) if prepaid.get('creator').toString() isnt req.user.id
return @sendForbiddenError(res) if _.size(prepaid.get('redeemers')) >= prepaid.get('maxRedeemers')
return @sendForbiddenError(res) if prepaid.get('redeemers')? and _.size(prepaid.get('redeemers')) >= prepaid.get('maxRedeemers')
return @sendForbiddenError(res) unless prepaid.get('type') is 'course'
return @sendForbiddenError(res) if prepaid.get('properties')?.endDate < new Date()
User.findById(req.body.userID).exec (err, user) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res, 'User for given ID not found') if not user
@ -85,14 +86,14 @@ PrepaidHandler = class PrepaidHandler extends Handler
query =
_id: prepaid.get('_id')
'redeemers.userID': { $ne: user.get('_id') }
$where: "this.redeemers.length < #{prepaid.get('maxRedeemers')}"
$where: "this.maxRedeemers > 0 && (!this.redeemers || this.redeemers.length < #{prepaid.get('maxRedeemers')})"
update = { $push: { redeemers : { date: new Date(), userID: userID } }}
Prepaid.update query, update, (err, nMatched) =>
return @sendDatabaseError(res, err) if err
if nMatched is 0
@logError(req.user, "POST prepaid redeemer lost race on maxRedeemers")
return @sendForbiddenError(res)
user.set('coursePrepaidID', prepaid.get('_id'))
user.save (err, user) =>
return @sendDatabaseError(res, err) if err
@ -100,7 +101,7 @@ PrepaidHandler = class PrepaidHandler extends Handler
redeemers = _.clone(prepaid.get('redeemers') or [])
redeemers.push({ date: new Date(), userID: userID })
prepaid.set('redeemers', redeemers)
@sendSuccess(res, @formatEntity(req, prepaid))
@sendSuccess(res, @formatEntity(req, prepaid))
createPrepaid: (user, type, maxRedeemers, properties, done) ->
Prepaid.generateNewCode (code) =>
@ -241,12 +242,15 @@ PrepaidHandler = class PrepaidHandler extends Handler
return @sendBadInputError(res, 'Bad creator') unless utils.isID creator
q = {
_id: {$gt: cutoffID}
creator: mongoose.Types.ObjectId(creator),
creator: mongoose.Types.ObjectId(creator)
type: 'course'
}
Prepaid.find q, (err, prepaids) =>
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, (@formatEntity(req, prepaid) for prepaid in prepaids))
documents = []
for prepaid in prepaids
documents.push(@formatEntity(req, prepaid)) unless prepaid.get('properties')?.endDate < new Date()
return @sendSuccess(res, documents)
else
super(arguments...)
@ -254,5 +258,5 @@ PrepaidHandler = class PrepaidHandler extends Handler
prepaid = super(req)
prepaid.set('redeemers', [])
return prepaid
module.exports = new PrepaidHandler()

View file

@ -11,7 +11,6 @@ if config.unittest
module.exports.api.send = ->
module.exports.templates =
parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
setup_free_sub_email: 'tem_sqdvLCZRwoDQc6jAf5RrQE'
share_progress_email: 'tem_VHE3ihhGmVa3727qds9zY8'
welcome_email: 'utnGaBHuSU4Hmsi7qrAypU'
ladder_update_email: 'JzaZxf39A4cKMxpPZUfWy4'
@ -23,4 +22,5 @@ module.exports.templates =
plain_text_email: 'tem_85UvKDCCNPXsFckERTig6Y'
next_steps_email: 'tem_RDHhTG5inXQi8pthyqWr5D'
course_invite_email: 'tem_u6D2EFWYC5Ptk38bSykjsU'
teacher_free_trial: 'tem_sqdvLCZRwoDQc6jAf5RrQE'
teacher_free_trial_hoc: 'tem_4ZSY9wsA9Qwn4wBFmZgPdc'

View file

@ -11,51 +11,41 @@ TrialRequestSchema = new mongoose.Schema {}, {strict: false, minimize: false, re
TrialRequestSchema.pre 'save', (next) ->
return next() unless @get('status') is 'approved'
# Add subscription
Prepaid.generateNewCode (code) =>
unless code
log.error "Trial request pre save prepaid gen new code failure"
return next()
# Add 2 course headcount
prepaid = new Prepaid
creator: @get('applicant')
type: 'course'
maxRedeemers: 2
properties:
trialRequestID: @get('_id')
prepaid.save (err) =>
if err
log.error "Trial request prepaid creation error: #{err}"
# Special HoC trial: Add 500 course headcount with end date
endDate = new Date()
endDate.setUTCMonth(endDate.getUTCMonth() + 2)
prepaid = new Prepaid
creator: @get('reviewer')
type: 'subscription'
maxRedeemers: 1
code: code
creator: @get('applicant')
type: 'course'
maxRedeemers: 500
properties:
couponID: 'free'
endDate: endDate
trialRequestID: @get('_id')
prepaid.save (err) =>
if err
log.error "Trial request prepaid creation error: #{err}"
return next()
@set('prepaidCode', code)
# Add 2 course headcount
prepaid = new Prepaid
creator: @get('applicant')
type: 'course'
maxRedeemers: 2
properties:
trialRequestID: @get('_id')
prepaid.save (err) =>
if err
log.error "Trial request prepaid creation error: #{err}"
next()
next()
TrialRequestSchema.post 'save', (doc) ->
if doc.get('status') is 'submitted'
msg = "<a href=\"http://codecombat.com/admin/trial-requests\">Trial Request</a> submitted by #{doc.get('properties').email}"
msg = "<a href=\"http://codecombat.com/admin/trial-requests\">Trial Request</a> submitted by #{doc.get('properties')?.email}"
hipchat.sendHipChatMessage msg, ['tower']
else if doc.get('status') is 'approved'
ppc = doc.get('prepaidCode')
unless ppc
log.error 'Trial request post save no ppc'
return
emailParams =
recipient:
address: doc.get('properties')?.email
email_id: sendwithus.templates.setup_free_sub_email
email_data:
url: "https://codecombat.com/account/subscription?_ppc=#{ppc}";
email_id: sendwithus.templates.teacher_free_trial_hoc
sendwithus.api.send emailParams, (err, result) =>
log.error "sendwithus trial request approved error: #{err}, result: #{result}" if err

View file

@ -33,7 +33,7 @@ describe '/db/prepaid', ->
clearModels [Course, CourseInstance, Payment, Prepaid, User], (err) ->
throw err if err
done()
describe 'POST /db/prepaid/<id>/redeemers', ->
it 'adds a given user to the redeemers property', (done) ->
@ -60,7 +60,7 @@ describe '/db/prepaid', ->
User.findById otherUser.id, (err, user) ->
expect(user.get('coursePrepaidID').equals(prepaid.get('_id'))).toBe(true)
done()
it 'does not allow more redeemers than maxRedeemers', (done) ->
loginNewUser (user1) ->
prepaid = new Prepaid({
@ -97,7 +97,7 @@ describe '/db/prepaid', ->
request.post {uri: url, json: redeemer }, (err, res, body) ->
expect(res.statusCode).toBe(403)
done()
it 'is idempotent across prepaids collection', (done) ->
loginNewUser (user1) ->
otherUser = new User({
@ -125,7 +125,7 @@ describe '/db/prepaid', ->
return done() unless res.statusCode is 200
expect(body.redeemers.length).toBe(0)
done()
it 'is idempotent to itself for a user other than the creator', (done) ->
loginNewUser (user1) ->
prepaid = new Prepaid({
@ -184,6 +184,101 @@ describe '/db/prepaid', ->
expect(res.statusCode).toBe(200)
done()
it 'return terminal prepaids', (done) ->
endDate = new Date()
endDate.setUTCMonth(endDate.getUTCMonth() + 2)
loginNewUser (user1) ->
prepaid = new Prepaid({
maxRedeemers: 500,
redeemers: [],
creator: user1.get('_id')
type: 'course'
properties:
endDate: endDate
})
prepaid.save (err, prepaid) ->
expect(err).toBeNull()
url = getURL("/db/prepaid?creator=#{user1.id}")
request.get {uri: url}, (err, res, body) ->
expect(res.statusCode).toBe(200)
documents = JSON.parse(body)
expect(documents.length).toEqual(1)
return done() unless documents.length is 1
expect(documents[0]?.properties?.endDate).toEqual(endDate.toISOString())
done()
it 'do not return expired terminal prepaids', (done) ->
endDate = new Date()
endDate.setUTCMonth(endDate.getUTCMonth() - 1)
loginNewUser (user1) ->
prepaid = new Prepaid({
maxRedeemers: 500,
redeemers: [],
creator: user1.get('_id')
type: 'course'
properties:
endDate: endDate
})
prepaid.save (err, prepaid) ->
expect(err).toBeNull()
url = getURL("/db/prepaid?creator=#{user1.id}")
request.get {uri: url}, (err, res, body) ->
expect(res.statusCode).toBe(200)
documents = JSON.parse(body)
expect(documents.length).toEqual(0)
done()
it 'redeem terminal prepaids', (done) ->
endDate = new Date()
endDate.setUTCMonth(endDate.getUTCMonth() + 2)
loginNewUser (user1) ->
prepaid = new Prepaid({
maxRedeemers: 500,
redeemers: [],
creator: user1.get('_id')
type: 'course'
properties:
endDate: endDate
})
prepaid.save (err, prepaid) ->
expect(err).toBeNull()
otherUser = new User()
otherUser.save (err, otherUser) ->
url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
redeemer = { userID: otherUser.id }
request.post {uri: url, json: redeemer }, (err, res, body) ->
expect(body.redeemers?.length).toBe(1)
expect(res.statusCode).toBe(200)
return done() unless res.statusCode is 200
prepaid = Prepaid.findById body._id, (err, prepaid) ->
expect(err).toBeNull()
expect(prepaid.get('redeemers').length).toBe(1)
User.findById otherUser.id, (err, user) ->
expect(user.get('coursePrepaidID').equals(prepaid.get('_id'))).toBe(true)
done()
it 'do not redeem expired terminal prepaids', (done) ->
endDate = new Date()
endDate.setUTCMonth(endDate.getUTCMonth() - 1)
loginNewUser (user1) ->
prepaid = new Prepaid({
maxRedeemers: 500,
redeemers: [],
creator: user1.get('_id')
type: 'course'
properties:
endDate: endDate
})
prepaid.save (err, prepaid) ->
expect(err).toBeNull()
otherUser = new User()
otherUser.save (err, otherUser) ->
url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
redeemer = { userID: otherUser.id }
request.post {uri: url, json: redeemer }, (err, res, body) ->
expect(res.statusCode).toBe(403)
done()
it 'Clear database', (done) ->
clearModels [Course, CourseInstance, Payment, Prepaid, User], (err) ->
throw err if err
@ -304,7 +399,7 @@ describe '/db/prepaid', ->
expect(err).toBeNull()
expect(res.statusCode).toBe(422)
done()
it 'Standard user purchases a prepaid for 1 seat', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
@ -324,7 +419,7 @@ describe '/db/prepaid', ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
verifyCoursePrepaid(user1, prepaid, done)
describe 'Purchase terminal_subscription', ->
it 'Anonymous submits a prepaid purchase', (done) ->
stripe.tokens.create {
@ -567,16 +662,16 @@ describe '/db/prepaid', ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginJoe (joe) ->
loginNewUser (user) ->
purchasePrepaid 'terminal_subscription', months: 1, 3, token.id, (err, res, prepaid) ->
request.get "#{getURL('/db/user')}/#{joe.id}/prepaid_codes", (err, res) ->
request.get "#{getURL('/db/user')}/#{user.id}/prepaid_codes", (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(200);
codes = JSON.parse res.body
expect(codes.length).toEqual(2)
expect(codes.length).toEqual(1)
expect(codes[0].maxRedeemers).toEqual(3)
expect(codes[0].properties).toBeDefined()
expect(codes[0].properties.months).toEqual(3)
expect(codes[0].properties.months).toEqual(1)
done()
it 'Test for injection', (done) ->

View file

@ -120,20 +120,24 @@ describe 'Trial Requests', ->
expect(body.reviewDate).toBeDefined()
expect(new Date(body.reviewDate)).toBeLessThan(new Date())
expect(body.reviewer).toEqual(admin.id)
expect(body.prepaidCode).toBeDefined()
TrialRequest.findById body._id, (err, doc) ->
expect(err).toBeNull()
expect(doc.get('status')).toEqual('approved')
expect(doc.get('reviewDate')).toBeDefined()
expect(new Date(doc.get('reviewDate'))).toBeLessThan(new Date())
expect(doc.get('reviewer')).toEqual(admin._id)
expect(doc.get('prepaidCode')).toBeDefined()
Prepaid.findOne {'properties.trialRequestID': doc.get('_id')}, (err, doc) ->
Prepaid.find {'properties.trialRequestID': doc.get('_id')}, (err, prepaids) ->
expect(err).toBeNull()
return done(err) if err
expect(doc.get('type')).toEqual('course')
expect(doc.get('creator')).toEqual(user.get('_id'))
expect(doc.get('maxRedeemers')).toEqual(2)
expect(prepaids.length).toEqual(2)
for prepaid in prepaids
expect(prepaid.get('type')).toEqual('course')
expect(prepaid.get('creator')).toEqual(user.get('_id'))
if prepaid.get('properties').endDate
expect(prepaid.get('maxRedeemers')).toEqual(500)
expect(prepaid.get('properties').endDate).toBeGreaterThan(new Date())
else
expect(prepaid.get('maxRedeemers')).toEqual(2)
done()
it 'Deny trial request', (done) ->