mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 00:31:21 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
7becb1db45
9 changed files with 124 additions and 6 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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}"
|
||||||
|
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 })
|
application.router.navigate(url, { trigger: true })
|
||||||
|
|
|
@ -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:`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()])
|
||||||
|
|
|
@ -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()
|
||||||
|
|
63
test/app/views/courses/TeacherCoursesView.spec.coffee
Normal file
63
test/app/views/courses/TeacherCoursesView.spec.coffee
Normal 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
|
Loading…
Reference in a new issue