diff --git a/app/views/courses/TeacherClassView.coffee b/app/views/courses/TeacherClassView.coffee index ce4c99c76..906337f42 100644 --- a/app/views/courses/TeacherClassView.coffee +++ b/app/views/courses/TeacherClassView.coffee @@ -71,6 +71,8 @@ module.exports = class TeacherClassView extends RootView @singleStudentLevelProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-level' @allStudentsLevelProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-all-students-single-level' + @debouncedRender = _.debounce @render + @state = new State(@getInitialState()) @updateHash @state.get('activeTab') # TODO: Don't push to URL history (maybe don't use url fragment for default tab) @@ -120,11 +122,6 @@ module.exports = class TeacherClassView extends RootView @attachMediatorEvents() attachMediatorEvents: () -> - @listenTo @state, 'sync change', -> - if _.isEmpty(_.omit(@state.changed, 'searchTerm')) - @renderSelectors('#enrollment-status-table') - else - @render() # Model/Collection events @listenTo @classroom, 'sync change update', -> classCode = @classroom.get('codeCamel') or @classroom.get('code') @@ -137,7 +134,6 @@ module.exports = class TeacherClassView extends RootView @state.set selectedCourse: @courses.first() unless @state.get('selectedCourse') @listenTo @courseInstances, 'sync change update', -> @setCourseMembers() - @render() # TODO: use state @listenTo @courseInstances, 'add-members', -> noty text: $.i18n.t('teacher.assigned'), layout: 'center', type: 'information', killer: true, timeout: 5000 @listenTo @students, 'sync change update add remove reset', -> @@ -149,7 +145,6 @@ module.exports = class TeacherClassView extends RootView @state.set students: @students @listenTo @students, 'sort', -> @state.set students: @students - @render() @listenTo @, 'course-select:change', ({ selectedCourse }) -> @state.set selectedCourse: selectedCourse @@ -162,6 +157,15 @@ module.exports = class TeacherClassView extends RootView onLoaded: -> @removeDeletedStudents() # TODO: Move this to mediator listeners? For both classroom and students? @calculateProgressAndLevels() + + # render callback setup + @listenTo @courseInstances, 'sync change update', @debouncedRender + @listenTo @state, 'sync change', -> + if _.isEmpty(_.omit(@state.changed, 'searchTerm')) + @renderSelectors('#enrollment-status-table') + else + @debouncedRender() + @listenTo @students, 'sort', @debouncedRender super() afterRender: -> diff --git a/app/views/editor/component/ThangComponentConfigView.coffee b/app/views/editor/component/ThangComponentConfigView.coffee index 878795081..ba95de63c 100644 --- a/app/views/editor/component/ThangComponentConfigView.coffee +++ b/app/views/editor/component/ThangComponentConfigView.coffee @@ -119,4 +119,4 @@ class SolutionsNode extends TreemaArrayNode succeeds: true }) - @set('/', solutions) \ No newline at end of file + @set('/', solutions) diff --git a/app/views/editor/level/treema_nodes.coffee b/app/views/editor/level/treema_nodes.coffee index 2cc00b7c3..ffa798b58 100644 --- a/app/views/editor/level/treema_nodes.coffee +++ b/app/views/editor/level/treema_nodes.coffee @@ -260,7 +260,10 @@ module.exports.ThangTypeNode = ThangTypeNode = class ThangTypeNode extends Treem thangType?.name or '...' getThangTypes: -> - return if ThangTypeNode.thangTypesCollection + if ThangTypeNode.thangTypesCollection + if not @constructor.thangTypes + @processThangTypes(ThangTypeNode.thangTypesCollection) + return ThangTypeNode.thangTypesCollection = new CocoCollection([], { url: '/db/thang.type' project:['name', 'components', 'original'] diff --git a/scripts/mail.coffee b/scripts/mail.coffee index c73d8ee41..c9ff590c4 100644 --- a/scripts/mail.coffee +++ b/scripts/mail.coffee @@ -91,7 +91,8 @@ emailUserInitialRecruiting = (user, callback) -> team = user.session.levelInfo.team team = team.substr(0, team.length - 1) context = - email_id: sendwithus.templates.recruiting_email + email_id: sendwithus.templates.recruiting_email.id + version_name: sendwithus.templates.recruiting_email.version recipient: address: if DEBUGGING then 'nick@codecombat.com' else user.email name: name @@ -134,7 +135,8 @@ emailUserTournamentResults = (winner, callback) -> name = winner.name team = winner.team.substr(0, winner.team.length - 1) context = - email_id: sendwithus.templates.greed_tournament_rank + email_id: sendwithus.templates.greed_tournament_rank.id + version_name: sendwithus.templates.greed_tournament_rank.version recipient: address: if DEBUGGING then 'nick@codecombat.com' else winner.email name: name diff --git a/scripts/updateCloseIoLeads.js b/scripts/updateCloseIoLeads.js index 6d9cfc029..8d1edce15 100644 --- a/scripts/updateCloseIoLeads.js +++ b/scripts/updateCloseIoLeads.js @@ -107,6 +107,7 @@ function upsertLeads(done) { function getCountryCode(country, emails) { // console.log(`DEBUG: getCountryCode ${country} ${emails.length}`); if (country) { + if (country.indexOf('Nederland') >= 0) return 'NL'; let countryCode = countryList.getCode(country); if (countryCode) return countryCode; } @@ -728,7 +729,7 @@ function createUpdateLeadFn(lead, existingLeads) { if (data.total_results === 0) { if (existingLeads[lead.name.toLowerCase()]) { if (existingLeads[lead.name.toLowerCase()].length === 1) { - console.log(`DEBUG: Using lead from email lookup: ${lead.name}`); + // console.log(`DEBUG: Using lead from email lookup: ${lead.name}`); return updateExistingLead(lead, existingLeads[lead.name.toLowerCase()][0], done); } console.error(`ERROR: ${existingLeads[lead.name.toLowerCase()].length} email leads found for ${lead.name}`); diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index aceff9437..c61941074 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -468,7 +468,8 @@ module.exports = class Handler notifyWatcherOfChange: (editor, watcher, changedDocument, editPath) -> context = - email_id: sendwithus.templates.change_made_notify_watcher + email_id: sendwithus.templates.change_made_notify_watcher.id + version_name: sendwithus.templates.change_made_notify_watcher.version recipient: address: watcher.get('email') name: watcher.get('name') diff --git a/server/handlers/course_instance_handler.coffee b/server/handlers/course_instance_handler.coffee index bf26df540..d7353f1ef 100644 --- a/server/handlers/course_instance_handler.coffee +++ b/server/handlers/course_instance_handler.coffee @@ -187,7 +187,8 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler return @sendForbiddenError(res) unless prepaid.get('maxRedeemers') > prepaid.get('redeemers').length for email in req.body.emails context = - email_id: sendwithus.templates.course_invite_email + email_id: sendwithus.templates.course_invite_email.id + version: sendwithus.templates.course_invite_email.version recipient: address: email subject: course.get('name') diff --git a/server/handlers/patch_handler.coffee b/server/handlers/patch_handler.coffee index b88f63a86..f47c88831 100644 --- a/server/handlers/patch_handler.coffee +++ b/server/handlers/patch_handler.coffee @@ -100,7 +100,8 @@ PatchHandler = class PatchHandler extends Handler sendPatchCreatedEmail: (patchCreator, watcher, patch, target, docLink) -> # return if watcher._id is patchCreator._id context = - email_id: sendwithus.templates.patch_created + email_id: sendwithus.templates.patch_created.id + version_name: sendwithus.templates.patch_created.version recipient: address: watcher.get('email') name: watcher.get('name') diff --git a/server/middleware/auth.coffee b/server/middleware/auth.coffee index 9f3c2f756..d60013ad1 100644 --- a/server/middleware/auth.coffee +++ b/server/middleware/auth.coffee @@ -52,7 +52,7 @@ module.exports = yield req.logInAsync(user) if req.query.callback - res.jsonp(req.user.toObject({req, publicOnly: true})) + res.jsonp(req.user.toObject({req, publicOnly: true})) else res.send(req.user.toObject({req, publicOnly: false})) res.end() @@ -132,7 +132,8 @@ module.exports = user.set('passwordReset', utils.getCodeCamel()) yield user.save() context = - email_id: sendwithus.templates.password_reset + email_id: sendwithus.templates.password_reset.id + version_name: sendwithus.templates.password_reset.version recipient: address: req.body.email email_data: diff --git a/server/middleware/classrooms.coffee b/server/middleware/classrooms.coffee index 1c2ec0f78..783e375e4 100644 --- a/server/middleware/classrooms.coffee +++ b/server/middleware/classrooms.coffee @@ -235,7 +235,8 @@ module.exports = for email in req.body.emails joinCode = (classroom.get('codeCamel') or classroom.get('code')) context = - email_id: sendwithus.templates.course_invite_email + email_id: sendwithus.templates.course_invite_email.id + version_name: sendwithus.templates.course_invite_email.version recipient: address: email email_data: diff --git a/server/middleware/users.coffee b/server/middleware/users.coffee index b334ca8f2..aff914d5b 100644 --- a/server/middleware/users.coffee +++ b/server/middleware/users.coffee @@ -85,7 +85,8 @@ module.exports = if not user throw new errors.NotFound('User not found') context = - email_id: sendwithus.templates.verify_email + email_id: sendwithus.templates.verify_email.id + version_name: sendwithus.templates.verify_email.version recipient: address: user.get('email') name: user.broadName() diff --git a/server/middleware/versions.coffee b/server/middleware/versions.coffee index aafebcb08..21cd9af90 100644 --- a/server/middleware/versions.coffee +++ b/server/middleware/versions.coffee @@ -107,7 +107,8 @@ module.exports = User.find({_id:{$in:watchers}}).select({email:1, name:1}).exec (err, watchers) -> for watcher in watchers context = - email_id: sendwithus.templates.change_made_notify_watcher + email_id: sendwithus.templates.change_made_notify_watcher.id + version_name: sendwithus.templates.change_made_notify_watcher.version recipient: address: watcher.get('email') name: watcher.get('name') @@ -127,7 +128,7 @@ module.exports = original = req.params.handle version = req.params.version if not database.isID(original) - throw new errors.UnprocessableEntity('Invalid MongoDB id: '+original) + throw new errors.UnprocessableEntity('Invalid MongoDB id: '+original) query = { 'original': mongoose.Types.ObjectId(original) } if version? diff --git a/server/routes/contact.coffee b/server/routes/contact.coffee index 651639bcb..fea0b2ff7 100644 --- a/server/routes/contact.coffee +++ b/server/routes/contact.coffee @@ -17,7 +17,7 @@ module.exports.setup = (app) -> req.user.update({$set: { enrollmentRequestSent: true }}).exec(_.noop) if req.body.recipientID is 'schools@codecombat.com' closeIO.sendMail fromAddress, subject, content, (err) -> log.error "Error sending contact form email via Close.io: #{err.message or err}" if err - else + else createSendWithUsContext req, fromAddress, subject, content, (context) -> sendwithus.api.send context, (err, result) -> log.error "Error sending contact form email via sendwithus: #{err.message or err}" if err @@ -61,7 +61,8 @@ createSendWithUsContext = (req, fromAddress, subject, content, done) -> else config.mail.supportPrimary context = - email_id: sendwithus.templates.plain_text_email + email_id: sendwithus.templates.plain_text_email.id + version_name: sendwithus.templates.plain_text_email.version recipient: address: toAddress sender: diff --git a/server/routes/mail.coffee b/server/routes/mail.coffee index b888f261e..d823d296c 100644 --- a/server/routes/mail.coffee +++ b/server/routes/mail.coffee @@ -592,7 +592,8 @@ sendLadderUpdateEmail = (session, now, daysAgo) -> sendEmail = (defeatContext, victoryContext, levelVersionsContext) -> # TODO: do something with the preferredLanguage? context = - email_id: sendwithus.templates.ladder_update_email + email_id: sendwithus.templates.ladder_update_email.id + version_name: sendwithus.templates.ladder_update_email.version recipient: address: if DEBUGGING then 'nick@codecombat.com' else user.get('email') name: name @@ -721,7 +722,8 @@ sendNextStepsEmail = (user, now, daysAgo) -> # Used to use these categories to customize the email; not doing it right now. TODO: customize it again in Sendwithus. # TODO: do something with the preferredLanguage? context = - email_id: sendwithus.templates.next_steps_email + email_id: sendwithus.templates.next_steps_email.id + version_name: sendwithus.templates.next_steps_email.version recipient: address: if DEBUGGING then 'nick@codecombat.com' else user.get('email') name: name diff --git a/server/sendwithus.coffee b/server/sendwithus.coffee index 23f834438..df6ba88c3 100644 --- a/server/sendwithus.coffee +++ b/server/sendwithus.coffee @@ -11,26 +11,30 @@ module.exports.api = send: (context, cb) -> log.debug('Tried to send email with context: ', JSON.stringify(context, null, ' ')) setTimeout(cb, 10) - + if swuAPIKey module.exports.api = new sendwithusAPI swuAPIKey, debug - + +# Version name can be supplied to tie a specific version to a deploy. +# That is most useful for testing templates with new data fields on staging. +# If it doesn't need to be synchronized to a deploy, you can just "publish" +# the new template version on SendWithUs (and leave this version blank) module.exports.templates = - parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud' - share_progress_email: 'tem_VHE3ihhGmVa3727qds9zY8' - welcome_email_user: 'tem_z7Xvj3mtWYk6ec6aW7RwFk' - welcome_email_student: 'tem_4WYPZNLzs5wawMF9qUJXUH' - verify_email: 'tem_zJee6uRsRmzqzktzneCkCn' - ladder_update_email: 'JzaZxf39A4cKMxpPZUfWy4' - patch_created: 'tem_xhxuNosLALsizTNojBjNcL' - change_made_notify_watcher: 'tem_7KVkfmv9SZETb25dtHbUtG' - recruiting_email: 'tem_mdFMgtcczHKYu94Jmq68j8' - greed_tournament_rank: 'tem_c4KYnk2TriEkkZx5NqqGLG' - generic_email: 'tem_JhRnQ4pvTS4KdQjYoZdbei' - plain_text_email: 'tem_85UvKDCCNPXsFckERTig6Y' - next_steps_email: 'tem_RDHhTG5inXQi8pthyqWr5D' - course_invite_email: 'tem_ic2ZhPkpj8GBADFuyAp4bj' - teacher_free_trial: 'tem_R7d9Hpoba9SceQNiYSXBak' - teacher_free_trial_hoc: 'tem_4ZSY9wsA9Qwn4wBFmZgPdc' - teacher_request_demo: 'tem_cwG3HZjEyb6QE493hZuUra' - password_reset: 'tem_wbQUMRtLY9xhec8BSCykLA' + parent_subscribe_email: { id: 'tem_2APERafogvwKhmcnouigud' } + share_progress_email: { id: 'tem_VHE3ihhGmVa3727qds9zY8' } + welcome_email_user: { id: 'tem_z7Xvj3mtWYk6ec6aW7RwFk' } + welcome_email_student: { id: 'tem_4WYPZNLzs5wawMF9qUJXUH' } + verify_email: { id: 'tem_zJee6uRsRmzqzktzneCkCn' } + ladder_update_email: { id: 'JzaZxf39A4cKMxpPZUfWy4' } + patch_created: { id: 'tem_xhxuNosLALsizTNojBjNcL' } + change_made_notify_watcher: { id: 'tem_7KVkfmv9SZETb25dtHbUtG' } + recruiting_email: { id: 'tem_mdFMgtcczHKYu94Jmq68j8' } + greed_tournament_rank: { id: 'tem_c4KYnk2TriEkkZx5NqqGLG' } + generic_email: { id: 'tem_JhRnQ4pvTS4KdQjYoZdbei' } + plain_text_email: { id: 'tem_85UvKDCCNPXsFckERTig6Y' } + next_steps_email: { id: 'tem_RDHhTG5inXQi8pthyqWr5D' } + course_invite_email: { id: 'tem_u6D2EFWYC5Ptk38bSykjsU', version: 'v3' } + teacher_free_trial: { id: 'tem_R7d9Hpoba9SceQNiYSXBak' } + teacher_free_trial_hoc: { id: 'tem_4ZSY9wsA9Qwn4wBFmZgPdc' } + teacher_request_demo: { id: 'tem_cwG3HZjEyb6QE493hZuUra' } + password_reset: { id: 'tem_wbQUMRtLY9xhec8BSCykLA' } diff --git a/test/app/views/teachers/TeacherClassView.spec.coffee b/test/app/views/teachers/TeacherClassView.spec.coffee index 57a5af175..065c128ee 100644 --- a/test/app/views/teachers/TeacherClassView.spec.coffee +++ b/test/app/views/teachers/TeacherClassView.spec.coffee @@ -75,24 +75,29 @@ describe 'TeacherClassView', -> # it "shows the classroom's join code" describe 'the Students tab', -> - beforeEach -> + beforeEach (done) -> @view.state.set('activeTab', '#students-tab') + _.defer(done) # it 'shows all of the students' # it 'sorts correctly by Name' # it 'sorts correctly by Progress' describe 'bulk-assign controls', -> - it 'shows alert when assigning course 2 to unenrolled students', -> + it 'shows alert when assigning course 2 to unenrolled students', (done) -> expect(@view.$('.cant-assign-to-unenrolled').hasClass('visible')).toBe(false) @view.$('.student-row .checkbox-flat').click() @view.$('.assign-to-selected-students').click() - expect(@view.$('.cant-assign-to-unenrolled').hasClass('visible')).toBe(true) + _.defer => + expect(@view.$('.cant-assign-to-unenrolled').hasClass('visible')).toBe(true) + done() - it 'shows alert when assigning but no students are selected', -> + it 'shows alert when assigning but no students are selected', (done) -> expect(@view.$('.no-students-selected').hasClass('visible')).toBe(false) @view.$('.assign-to-selected-students').click() - expect(@view.$('.no-students-selected').hasClass('visible')).toBe(true) + _.defer => + expect(@view.$('.no-students-selected').hasClass('visible')).toBe(true) + done() # describe 'the Course Progress tab', -> # it 'shows the correct Course Overview progress'