Merge branch 'master' into production

This commit is contained in:
Phoenix Eliot 2016-08-16 11:31:42 -07:00
commit 7becb1db45
9 changed files with 124 additions and 6 deletions

View file

@ -97,6 +97,7 @@ module.exports = TestView = class TestView extends RootView
jasmine.Ajax.install() jasmine.Ajax.install()
beforeEach -> beforeEach ->
me.clear()
jasmine.Ajax.requests.reset() jasmine.Ajax.requests.reset()
Backbone.Mediator.init() Backbone.Mediator.init()
Backbone.Mediator.setValidationEnabled false Backbone.Mediator.setValidationEnabled false

View file

@ -222,6 +222,7 @@ module.exports = class CocoView extends Backbone.View
window.currentModal = modalView window.currentModal = modalView
@getRootView().stopListeningToShortcuts(true) @getRootView().stopListeningToShortcuts(true)
Backbone.Mediator.publish 'modal:opened', {} Backbone.Mediator.publish 'modal:opened', {}
modalView
modalClosed: => modalClosed: =>
visibleModal.willDisappear() if visibleModal visibleModal.willDisappear() if visibleModal

View file

@ -50,9 +50,13 @@ module.exports = class ModalView extends CocoView
$el = @$el.find('.modal-body') unless $el $el = @$el.find('.modal-body') unless $el
super($el) super($el)
# TODO: Combine hide/onHidden such that backbone 'hide/hidden.bs.modal' events and our 'hide/hidden' events are more 1-to-1
# For example:
# pressing 'esc' or using `currentModal.hide()` triggers 'hide', 'hide.bs.modal', 'hidden', 'hidden.bs.modal'
# clicking outside the modal triggers 'hide.bs.modal', 'hidden', 'hidden.bs.modal' (but not 'hide')
hide: -> hide: ->
@trigger 'hide' @trigger 'hide'
@$el.removeClass('fade').modal 'hide' @$el.removeClass('fade').modal 'hide' unless @destroyed
onHidden: -> onHidden: ->
@trigger 'hidden' @trigger 'hidden'

View file

@ -9,6 +9,7 @@ User = require 'models/User'
CourseInstance = require 'models/CourseInstance' CourseInstance = require 'models/CourseInstance'
RootView = require 'views/core/RootView' RootView = require 'views/core/RootView'
template = require 'templates/courses/teacher-courses-view' template = require 'templates/courses/teacher-courses-view'
HeroSelectModal = require 'views/courses/HeroSelectModal'
module.exports = class TeacherCoursesView extends RootView module.exports = class TeacherCoursesView extends RootView
id: 'teacher-courses-view' id: 'teacher-courses-view'
@ -66,4 +67,10 @@ module.exports = class TeacherCoursesView extends RootView
language = form.find('.language-select').val() language = form.find('.language-select').val()
window.tracker?.trackEvent 'Classes Guides Play Level', category: 'Teachers', courseID: courseID, language: language, levelSlug: levelSlug, ['Mixpanel'] window.tracker?.trackEvent 'Classes Guides Play Level', category: 'Teachers', courseID: courseID, language: language, levelSlug: levelSlug, ['Mixpanel']
url = "/play/level/#{levelSlug}?course=#{courseID}&codeLanguage=#{language}" url = "/play/level/#{levelSlug}?course=#{courseID}&codeLanguage=#{language}"
application.router.navigate(url, { trigger: true }) firstLevelSlug = @campaigns.get(@courses.at(0).get('campaignID')).getLevels().at(0).get('slug')
if levelSlug is firstLevelSlug
@listenToOnce @openModalView(new HeroSelectModal()),
'hidden': ->
application.router.navigate(url, { trigger: true })
else
application.router.navigate(url, { trigger: true })

View file

@ -6,6 +6,10 @@ if (process.argv.length !== 4) {
process.exit(); process.exit();
} }
// NOTE: last_activity_date_range is the contacted at date, UTC
// TODO: Only looking at contacts created in last 30 days. How do we catch replies for contacts older than 30 days?
const closeIoApiKey = process.argv[2]; const closeIoApiKey = process.argv[2];
const zpAuthToken = process.argv[3]; const zpAuthToken = process.argv[3];
@ -15,6 +19,9 @@ const async = require('async');
const request = require('request'); const request = require('request');
const zpPageSize = 100; const zpPageSize = 100;
let zpMinActivityDate = new Date();
zpMinActivityDate.setUTCDate(zpMinActivityDate.getUTCDate() - 30);
zpMinActivityDate = zpMinActivityDate.toISOString().substring(0, 10);
getZPContacts((err, emailContactMap) => { getZPContacts((err, emailContactMap) => {
if (err) { if (err) {
@ -39,7 +46,6 @@ function createCloseLead(zpContact, done) {
contacts: [ contacts: [
{ {
name: zpContact.name, name: zpContact.name,
title: zpContact.title,
emails: [{email: zpContact.email}] emails: [{email: zpContact.email}]
} }
], ],
@ -51,6 +57,9 @@ function createCloseLead(zpContact, done) {
if (zpContact.phone) { if (zpContact.phone) {
postData.contacts[0].phones = [{phone: zpContact.phone}]; postData.contacts[0].phones = [{phone: zpContact.phone}];
} }
if (zpContact.title) {
postData.contacts[0].title = zpContact.title;
}
if (zpContact.district) { if (zpContact.district) {
postData.custom['demo_nces_district'] = zpContact.district; postData.custom['demo_nces_district'] = zpContact.district;
postData.custom['demo_nces_name'] = zpContact.organization; postData.custom['demo_nces_name'] = zpContact.organization;
@ -189,7 +198,7 @@ function getZPContactsPage(contacts, searchQuery, done) {
function createGetZPAutoResponderContactsPage(contacts, page) { function createGetZPAutoResponderContactsPage(contacts, page) {
return (done) => { return (done) => {
// console.log(`DEBUG: Fetching autoresponder page ${page} ${zpPageSize}...`); // console.log(`DEBUG: Fetching autoresponder page ${page} ${zpPageSize}...`);
let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&contact_email_autoresponder=true`; let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&last_activity_date_range[min]=${zpMinActivityDate}&contact_email_autoresponder=true`;
getZPContactsPage(contacts, searchQuery, done); getZPContactsPage(contacts, searchQuery, done);
}; };
} }
@ -197,7 +206,7 @@ function createGetZPAutoResponderContactsPage(contacts, page) {
function createGetZPRepliedContactsPage(contacts, page) { function createGetZPRepliedContactsPage(contacts, page) {
return (done) => { return (done) => {
// console.log(`DEBUG: Fetching email reply page ${page} ${zpPageSize}...`); // console.log(`DEBUG: Fetching email reply page ${page} ${zpPageSize}...`);
let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&contact_email_replied=true`; let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&last_activity_date_range[min]=${zpMinActivityDate}&contact_email_replied=true`;
getZPContactsPage(contacts, searchQuery, done); getZPContactsPage(contacts, searchQuery, done);
}; };
} }
@ -222,7 +231,7 @@ function getZPContacts(done) {
if (err) return done(err); if (err) return done(err);
const emailContactMap = {}; const emailContactMap = {};
for (const contact of contacts) { for (const contact of contacts) {
if (!contact.organization || !contact.name || !contact.title || !contact.email) { if (!contact.organization || !contact.name || !contact.email) {
console.log(JSON.stringify(contact, null, 2)); console.log(JSON.stringify(contact, null, 2));
return done(`DEBUG: missing data for zp contact:`); return done(`DEBUG: missing data for zp contact:`);
} }

View file

@ -1,6 +1,7 @@
Level = require 'models/Level' Level = require 'models/Level'
Course = require 'models/Course' Course = require 'models/Course'
Courses = require 'collections/Courses' Courses = require 'collections/Courses'
Campaign = require 'models/Campaign'
User = require 'models/User' User = require 'models/User'
Classroom = require 'models/Classroom' Classroom = require 'models/Classroom'
LevelSession = require 'models/LevelSession' LevelSession = require 'models/LevelSession'
@ -19,16 +20,34 @@ module.exports = {
_id: _id _id: _id
name: _.string.humanize(_id) name: _.string.humanize(_id)
releasePhase: 'released' releasePhase: 'released'
concepts: []
}, attrs) }, attrs)
attrs.campaignID ?= sources.campaign?.id or _.uniqueId('campaign_') attrs.campaignID ?= sources.campaign?.id or _.uniqueId('campaign_')
return new Course(attrs) return new Course(attrs)
makeCampaign: (attrs, sources={}) ->
_id = _.uniqueId('campaign_')
attrs = _.extend({}, {
_id
name: _.string.humanize(_id)
levels: [@makeLevel(), @makeLevel()]
}, attrs)
if sources.levels
levelsMap = {}
sources.levels.each (level) ->
levelsMap[level.id] = level
attrs.levels = levelsMap
return new Campaign(attrs)
makeLevel: (attrs) -> makeLevel: (attrs) ->
_id = _.uniqueId('level_') _id = _.uniqueId('level_')
attrs = _.extend({}, { attrs = _.extend({}, {
_id: _id _id: _id
name: _.string.humanize(_id) name: _.string.humanize(_id)
slug: _.string.dasherize(_id)
original: _id+'_original' original: _id+'_original'
version: version:
major: 0 major: 0

View file

@ -13,6 +13,7 @@ describe 'CoursesView', ->
describe 'Change Hero button', -> describe 'Change Hero button', ->
beforeEach (done) -> beforeEach (done) ->
me.set(factories.makeUser({ role: 'student' }).attributes)
view = new CoursesView() view = new CoursesView()
classrooms = new Classrooms([factories.makeClassroom()]) classrooms = new Classrooms([factories.makeClassroom()])
courseInstances = new CourseInstances([factories.makeCourseInstance()]) courseInstances = new CourseInstances([factories.makeCourseInstance()])

View file

@ -35,3 +35,16 @@ describe 'HeroSelectModal', ->
expect(request.method).toBe("PUT") expect(request.method).toBe("PUT")
expect(JSON.parse(request.params).heroConfig?.thangType).toBe(hero2.get('original')) expect(JSON.parse(request.params).heroConfig?.thangType).toBe(hero2.get('original'))
done() done()
it 'triggers its events properly', (done) ->
spyOn(modal, 'trigger')
modal.render()
modal.$('.hero-option:nth-child(2)').click()
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({ status: 200, responseText: me.attributes })
expect(modal.trigger).toHaveBeenCalled()
expect(modal.trigger.calls.argsFor(0)[0]).toBe('hero-select:success')
expect(modal.trigger).not.toHaveBeenCalledWith('hide')
modal.$('.select-hero-btn').click()
expect(modal.trigger).toHaveBeenCalledWith('hide')
done()

View file

@ -0,0 +1,63 @@
TeacherCoursesView = require 'views/courses/TeacherCoursesView'
HeroSelectModal = require 'views/courses/HeroSelectModal'
Classrooms = require 'collections/Classrooms'
Courses = require 'collections/Courses'
Campaigns = require 'collections/Campaigns'
Levels = require 'collections/Levels'
auth = require 'core/auth'
factories = require 'test/app/factories'
describe 'TeacherCoursesView', ->
modal = null
view = null
describe 'Play Level form', ->
beforeEach (done) ->
me.set(factories.makeUser({ role: 'teacher' }).attributes)
view = new TeacherCoursesView()
classrooms = new Classrooms([factories.makeClassroom()])
levels1 = new Levels([ factories.makeLevel({ name: 'Dungeons of Kithgard' }), factories.makeLevel(), factories.makeLevel() ])
levels2 = new Levels([ factories.makeLevel(), factories.makeLevel(), factories.makeLevel() ])
campaigns = new Campaigns([factories.makeCampaign({}, { levels: levels1 }), factories.makeCampaign({}, { levels: levels2 })])
courses = new Courses([factories.makeCourse({}, {campaign: campaigns.at(0)}), factories.makeCourse({}, {campaign: campaigns.at(1)})])
view.ownedClassrooms.fakeRequests[0].respondWith({ status: 200, responseText: classrooms.stringify() })
view.campaigns.fakeRequests[0].respondWith({ status: 200, responseText: campaigns.stringify() })
view.courses.fakeRequests[0].respondWith({ status: 200, responseText: courses.stringify() })
view.render()
done()
it 'opens HeroSelectModal for the first level of the first course', (done) ->
spyOn(view, 'openModalView').and.callFake (modal) -> modal
spyOn(application.router, 'navigate')
view.$('.play-level-button').first().click()
expect(view.openModalView).toHaveBeenCalled()
expect(application.router.navigate).not.toHaveBeenCalled()
args = view.openModalView.calls.argsFor(0)
modalView = args[0]
expect(modalView instanceof HeroSelectModal).toBe(true)
modalView.trigger('hero-select:success')
expect(application.router.navigate).not.toHaveBeenCalled()
modalView.trigger('hide')
modalView.trigger('hidden')
_.defer ->
expect(application.router.navigate).toHaveBeenCalled()
done()
it "doesn't open HeroSelectModal for other levels", ->
spyOn(view, 'openModalView')
spyOn(application.router, 'navigate')
secondLevelSlug = view.$('.level-select:first option:nth-child(2)').val()
view.$('.level-select').first().val(secondLevelSlug)
view.$('.play-level-button').first().click()
expect(view.openModalView).not.toHaveBeenCalled()
expect(application.router.navigate).toHaveBeenCalled()
it "doesn't open HeroSelectModal for other courses", ->
spyOn(view, 'openModalView')
spyOn(application.router, 'navigate')
view.$('.play-level-button').get(1).click()
expect(view.openModalView).not.toHaveBeenCalled()
expect(application.router.navigate).toHaveBeenCalled()
it "remembers the selected hero" # TODO