Merge branch 'master' into production

This commit is contained in:
phoenixeliot 2016-07-25 11:12:41 -07:00
commit d1206ba692
11 changed files with 356 additions and 193 deletions

View file

@ -17,6 +17,9 @@
<script src="/javascripts/app/vendor/aether-html.js"></script> <script src="/javascripts/app/vendor/aether-html.js"></script>
<style> <style>
@import 'https://fonts.googleapis.com/css?family=Holtwood+One+SC';
/* Import that font for demoing web-dev levels until @import ordering bug is fixed */
* { * {
transition: 1s ease-in-out; transition: 1s ease-in-out;
} }

View file

@ -209,10 +209,10 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
not: "no" not: "no"
"!": "no" "!": "no"
"=": "asigne a" "=": "asigne a"
"==": "iguala" "==": "igual a"
"===": "iguala estrictamente" "===": "igual a estrictamente"
"!=": "no iguala" "!=": "no igual a"
# "!==": "does not strictly equal" "!==": "no estrictamente igual"
">": "es mayor que" ">": "es mayor que"
">=": "es mayor que o igual" ">=": "es mayor que o igual"
"<": "es menor que" "<": "es menor que"
@ -511,14 +511,14 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
tip_mistakes_proof_of_trying: "Errores en tu código son solo evidencia de que estas intentando." tip_mistakes_proof_of_trying: "Errores en tu código son solo evidencia de que estas intentando."
# tip_adding_orgres: "Rounding up ogros." # tip_adding_orgres: "Rounding up ogros."
tip_sharpening_swords: "Afilando las espadas." tip_sharpening_swords: "Afilando las espadas."
# tip_ratatouille: "You must not let anyone define your limits because of where you come from. Your only limit is your soul. - Gusteau, Ratatouille" tip_ratatouille: "No debes dejar que nadie defina tus límites a causa de donde venas. Tu único límite es tu alma. - Gusteau, Ratatouille"
tip_nemo: "¿Cuando huye la suerte, sabes que hay que hacer? Sigue nadando, sigue nadando. - Dory, Finding Nemo" tip_nemo: "¿Cuando huye la suerte, sabes que hay que hacer? Sigue nadando, sigue nadando. - Dory, Finding Nemo"
# tip_internet_weather: "Just move to the internet, it's great here. We get to live inside where the weather is always awesome. - John Green" tip_internet_weather: "Debes moverte al intenet, es genial aquí. Tenemos la oportunidad de vivir dentro donde el clima es siempre sorprendente. - John Green"
# tip_nerds: "Nerds are allowed to love stuff, like jump-up-and-down-in-the-chair-can't-control-yourself love it. - John Green" tip_nerds: "A los Nerds se les permite amor cosas, como saltar-arriba-y-abajo-en-la-silla-no-te-puedes-controlar-tu-mismo quiérelo. - John Green"
# tip_self_taught: "I taught myself 90% of what I've learned. And that's normal! - Hank Green" tip_self_taught: "Me enseñé a mí mismo el 90% de lo que he aprendido. ¡Y eso es normal! - Hank Green"
tip_luna_lovegood: "No te preocupes, estas tan cuerdo como yo. - Luna Lovegood" tip_luna_lovegood: "No te preocupes, estas tan cuerdo como yo. - Luna Lovegood"
# tip_good_idea: "The best way to have a good idea is to have a lot of ideas. - Linus Pauling" tip_good_idea: "La mejor forma de tener una buena idea es tener un montón de ideas. - Linus Pauling"
# tip_programming_not_about_computers: "La ciencia cpomputacional is no more about computers than astronomy is about telescopes. - Edsger Dijkstra" tip_programming_not_about_computers: "En las Ciencias de la Computación no es más acerca de computadoras que la astronomía acerca de telescopios. - Edsger Dijkstra"
tip_mulan: "Si crees que puedes, entonces lo harás. - Mulan" tip_mulan: "Si crees que puedes, entonces lo harás. - Mulan"
game_menu: game_menu:
@ -755,7 +755,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
josh_blurb: "El piso es Lava" josh_blurb: "El piso es Lava"
phoenix_title: "Ingeniero de Software" phoenix_title: "Ingeniero de Software"
nolan_title: "Administrador de territorio" nolan_title: "Administrador de territorio"
# elliot_title: "Administrador de Partnership" elliot_title: "Administrador de Asociación"
retrostyle_title: "Ilustración" retrostyle_title: "Ilustración"
retrostyle_blurb: "Juegos con estilo Retro" retrostyle_blurb: "Juegos con estilo Retro"
jose_title: "Música" jose_title: "Música"
@ -827,14 +827,14 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
primary_role_label: "Tu Rol Primario" primary_role_label: "Tu Rol Primario"
role_default: "Seleccione Rol" role_default: "Seleccione Rol"
primary_role_default: "Seleccione Rol Primario" primary_role_default: "Seleccione Rol Primario"
# purchaser_role_default: "Seleccione Purchaser Role" purchaser_role_default: "Seleccione Rol de Comprador"
tech_coordinator: "Coordinador de tecnología" tech_coordinator: "Coordinador de tecnología"
advisor: "Tutor" advisor: "Tutor"
principal: "Director" principal: "Director"
superintendent: "Supervisor" superintendent: "Supervisor"
parent: "Padre" parent: "Padre"
# purchaser_role_label: "Your Purchaser Role" purchaser_role_label: "Tu Rol de Comprador"
# influence_advocate: "Influence/Advocate" influence_advocate: "Influencia/Partidario"
evaluate_recommend: "Evaluar/Recomendar" evaluate_recommend: "Evaluar/Recomendar"
approve_funds: "Aprobar Fondos" approve_funds: "Aprobar Fondos"
# no_purchaser_role: "No role in purchase decisions" # no_purchaser_role: "No role in purchase decisions"
@ -1245,8 +1245,8 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
additional_resources_4_pref: "Consulte nuestra" additional_resources_4_pref: "Consulte nuestra"
additional_resources_4_mid: "Página de Escuelas" additional_resources_4_mid: "Página de Escuelas"
additional_resources_4_suff: "para aprender más sobre la oferta para el aula de CodeCombat." additional_resources_4_suff: "para aprender más sobre la oferta para el aula de CodeCombat."
# educator_wiki_pref: "Or check out our new" educator_wiki_pref: "O echa un vistazo a nuestra nieva"
# educator_wiki_mid: "educator wiki" educator_wiki_mid: "wiki de educador"
educator_wiki_suff: "busca en nuestra guía en línea." educator_wiki_suff: "busca en nuestra guía en línea."
your_classes: "Tus Clases" your_classes: "Tus Clases"
no_classes: "Aún no hay clases!" no_classes: "Aún no hay clases!"
@ -1278,105 +1278,105 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
language_cannot_change: "El lenguaje no puede ser cambiado una vez que el estudiante ingreso a la clase." language_cannot_change: "El lenguaje no puede ser cambiado una vez que el estudiante ingreso a la clase."
learn_p: "Aprender Python" learn_p: "Aprender Python"
learn_j: "Aprender JavaScript" learn_j: "Aprender JavaScript"
# avg_student_exp_label: "Average Student Programming Experience" avg_student_exp_label: "Promedio de la experiencia en programación del estudiante"
# avg_student_exp_desc: "This will help us understand how to pace courses better." avg_student_exp_desc: "Esto nos ayudará a entender como llevar el ritmo de los cursos."
# avg_student_exp_select: "Select the best option" avg_student_exp_select: "Selecciona la mejor opción"
# avg_student_exp_none: "No Experience - little to no experience" avg_student_exp_none: "Sin experiencia - poca o ninguna experiencia"
# avg_student_exp_beginner: "Beginner - some exposure or block-based" avg_student_exp_beginner: "Principiante - cierto grado de exposición o basado en bloque"
# avg_student_exp_intermediate: "Intermediate - some experience with typed code" avg_student_exp_intermediate: "Intermedio - un poco de experiencia con código tipeado"
# avg_student_exp_advanced: "Advanced - extensive experience with typed code" avg_student_exp_advanced: "Avanzado - amplia experiencia con código tipeado"
# avg_student_exp_varied: "Varied Levels of Experience" avg_student_exp_varied: "Niveles Variados de Experiencia"
# student_age_range_label: "Student Age Range" student_age_range_label: "Rango de Edad del Estudiante"
student_age_range_younger: "Menor que than 6" student_age_range_younger: "Menor que 6"
student_age_range_older: "Mayor que 18" student_age_range_older: "Mayor que 18"
# student_age_range_to: "to" student_age_range_to: "a"
create_class: "Crear Grupo" create_class: "Crear Grupo"
class_name: "Nombre de clase" class_name: "Nombre de clase"
# teacher_account_restricted: "Your account is a teacher account, and so cannot access student content." teacher_account_restricted: "Tu cuenta es una cuenta de maestro, y no puedes acceder al contenido del estudiante."
teacher: teacher:
# teacher_dashboard: "Teacher Dashboard" # Navbar teacher_dashboard: "Tablero del maestro" # Navbar
# my_classes: "My Classes" my_classes: "Mis Clases"
# courses: "Courses" courses: "Cursos"
enrollments: "Recursos" enrollments: "Recursos"
resources: "Resources" resources: "Resources"
help: "Ayuda" help: "Ayuda"
# students: "Students" # Shared students: "Estudiantes" # Shared
language: "Lenguaje" language: "Lenguaje"
# edit_class_settings: "edit class settings" edit_class_settings: "editar configuración de la clase"
# complete: "Complete" complete: "Completo"
# access_restricted: "Account Update Required" access_restricted: "Se requiere Actualización de la Cuenta"
# teacher_account_required: "A teacher account is required to access this content." teacher_account_required: "Una cuenta de Maestro es requerida para acceder a este contenido."
create_teacher_account: "Crear Cuenta de Maestra" create_teacher_account: "Crear Cuenta de Maestro"
what_is_a_teacher_account: "Cuál es una Cuenta de Maestra?" what_is_a_teacher_account: "Qué es una Cuenta de Maestro?"
# teacher_account_explanation: "Una Cuenta de Maestra en CodeCombat Teacher da permiso a crear grupo, monitor students progress as they work through courses, manage enrollments and access resources to aid in your curriculum-building." teacher_account_explanation: "Una Cuenta de Maestro en CodeCombat da permiso a crear salones de clases, monitorear el progreso de los estudiantes mientras ellos trabajan a través de los cursos, manejar inscripciones y acceder a recursos para la creación de su plan de estudio."
# current_classes: "Current Classes" current_classes: "Clases Recientes"
# archived_classes: "Archived Classes" archived_classes: "Clases Archivadas"
# archived_classes_blurb: "Classes can be archived for future reference. Unarchive a class to view it in the Current Classes list again." archived_classes_blurb: "Las Clases pueden ser archivadas para futuras referencias. Desarchiva una Clase para verla en la lista de Clases Recientes de nuevo."
# view_class: "view class" view_class: "ver clase"
# archive_class: "archive class" archive_class: "almacenar clase"
# unarchive_class: "unarchive class" unarchive_class: "desarchivar clase"
# unarchive_this_class: "Unarchive this class" unarchive_this_class: "desarchivar esta clase"
# no_students_yet: "This class has no students yet." no_students_yet: "Esta clase no tienes estudiantes todavía."
# add_students: "Add Students" add_students: "Agregar Estudiantes"
# create_new_class: "Create a New Class" create_new_class: "Crear una Nueva Clase"
# class_overview: "Class Overview" # View Class page class_overview: "Resumen de la Clase" # View Class page
# avg_playtime: "Average level playtime" avg_playtime: "Nivel medio de tiempo de juego"
# total_playtime: "Total play time" total_playtime: "Tiempo Total de juego"
# avg_completed: "Average levels completed" avg_completed: "Promedio de niveles completados"
# total_completed: "Total levels completed" total_completed: "Total de niveles completados"
# created: "Created" created: "Creado"
# concepts_covered: "Concepts covered" concepts_covered: "Conceptos cubiertos"
# earliest_incomplete: "Earliest incomplete level" earliest_incomplete: "Nivel incompleto más Reciente"
# latest_complete: "Latest completed level" latest_complete: "Último nivel completado"
# enroll_student: "Enroll student" enroll_student: "Inscribir estudiante"
# adding_students: "Adding students" adding_students: "Agregar estudiantes"
# course_progress: "Course Progress" course_progress: "Progreso del curso"
# not_applicable: "N/A" # not_applicable: "N/A"
# edit: "edit" edit: "editar"
# remove: "remove" remove: "eliminar"
# latest_completed: "Latest Completed" latest_completed: "Último Completado"
# sort_by: "Sort by" sort_by: "Ordenado por"
# progress: "Progress" progress: "Progreso"
# completed: "Completed" completed: "Completado"
# started: "Started" started: "Iniciado"
# click_to_view_progress: "click to view progress" click_to_view_progress: "click para ver el progreso"
# no_progress: "No progress" no_progress: "Sin progreso"
# select_course: "Select course to view" select_course: "Selecciona el curso a ver"
# course_overview: "Course Overview" course_overview: "Resumen del Curso"
# copy_class_code: "Copy Class Code" copy_class_code: "Copiar código de la Clase"
# class_code_blurb: "New students can enter this class code on their dashboard or visit codecombat.com/courses to join the class." class_code_blurb: "Nuevos estudiantes pueden entrar a este código de la clase en su tablero o visitar codecombat.com/courses para unirse a la clase."
# copy_class_url: "Copy Class URL" copy_class_url: "Copia la URL de la Clase"
# class_join_url_blurb: "New students can visit this URL while logged in to join the class." class_join_url_blurb: "Nuevos estudiantes pueden visitar esta URL mientras esten logeados para unirse a la clase."
# add_students_manually: "Add Students Manually" add_students_manually: "Agregar Estudiantes Manualmente"
# bulk_assign: "Bulk-assign" bulk_assign: "Mayor a asignar"
# assign_to_selected_students: "Assign to Selected Students" assign_to_selected_students: "Asignar a los Estudiantes Seleccionados"
# assigned: "Assigned" assigned: "Asignado"
# enroll_selected_students: "Enroll Selected Students" enroll_selected_students: "Inscribir Estudiantes Seleccionados"
# cant_assign_to_unenrolled: "Course cannot be assigned to students who are not enrolled." cant_assign_to_unenrolled: "El curso no puede ser asignado a estudiantes que no están inscritos."
# no_students_selected: "No students were selected." no_students_selected: "No fueron seleccionados estudiantes."
# guides_coming_soon: "Guides coming soon!" # Courses guides_coming_soon: "¡Guías próximamente!" # Courses
# show_students_from: "Show students from" # Enroll students modal show_students_from: "Muestra estudiantes de" # Enroll students modal
# enroll_the_following_students: "Enroll the following students" enroll_the_following_students: "Inscribir los siguientes estudianes"
# all_students: "All Students" all_students: "Todos los estudiantes"
# enroll_students: "Enroll Students" enroll_students: "Inscribir Estudiantes"
# not_enough_enrollments: "Not enough Enrollments available." not_enough_enrollments: "No tienes suficientes Inscripciones disponibles."
# enrollments_blurb_1: "Students taking Computer Science" # Enrollments page enrollments_blurb_1: "Estudiantes tomando Ciencias de la Computación" # Enrollments page
# enrollments_blurb_2: "require enrollments to access the courses." enrollments_blurb_2: "se requieren inscritos para acceder al curso."
# credits_available: "Credits Available" credits_available: "Cŕeditos Disponibles"
# total_unique_students: "Total Unique Students" total_unique_students: "Total de Estudiantes Únicos"
# total_enrolled_students: "Total Enrolled Students" total_enrolled_students: "Total de Estudiantes Inscritos"
# unenrolled_students: "Unenrolled Students" unenrolled_students: "Estudiantes no Inscritos"
# add_enrollment_credits: "Add Enrollment Credits" add_enrollment_credits: "Agregar Cŕeditos de Inscripción"
purchasing: "Adquiriendo..." purchasing: "Adquiriendo..."
purchased: "Adquirido!" purchased: "Adquirido!"
# purchase_now: "Purchase Now" purchase_now: "Adquirir Ahora"
# how_to_enroll: "How to Enroll Students" how_to_enroll: "¿Cómo inscribir estudiantes?"
how_to_enroll_blurb_1: "Si un estudiante no está inscrito aún, encontrará un botón \"Inscribirse\" al lado del progreso de curso en su clase." how_to_enroll_blurb_1: "Si un estudiante no está inscrito aún, encontrará un botón \"Inscribirse\" al lado del progreso de curso en su clase."
how_to_enroll_blurb_2: "Para inscribir en bloque a estudiantes, seleccionelos utilizando la casilla de verificación al lado izquierdo de la página de la clase, luego de clic al botón \"Inscribir Estudiantes Seleccionados\" ." how_to_enroll_blurb_2: "Para inscribir en bloque a estudiantes, seleccionelos utilizando la casilla de verificación al lado izquierdo de la página de la clase, luego de clic al botón \"Inscribir Estudiantes Seleccionados\" ."
how_to_enroll_blurb_3: "Una vez que el estudiante se ha inscrito, podrá acceder a todo el contenido del curso." how_to_enroll_blurb_3: "Una vez que el estudiante se ha inscrito, podrá acceder a todo el contenido del curso."
bulk_pricing_blurb: "Quieres comprar más de 25 cuentas de estudiante? Contáctanos para hablar del siguiente paso." bulk_pricing_blurb: "Quieres comprar más de 25 cuentas de estudiante? Contáctanos para hablar del siguiente paso."
# total_unenrolled: "Total unenrolled" total_unenrolled: "Total de Estudiantes no Inscritos"
export_student_progress: "Exportar el Progreso del Estudiante(CSV)" export_student_progress: "Exportar el Progreso del Estudiante(CSV)"
classes: classes:

View file

@ -89,7 +89,7 @@ module.exports = class User extends CocoModel
isSessionless: -> isSessionless: ->
# TODO: Fix old users who got mis-tagged as teachers # TODO: Fix old users who got mis-tagged as teachers
# TODO: Should this just be isTeacher, eventually? # TODO: Should this just be isTeacher, eventually?
Boolean(me.isTeacher() and utils.getQueryVariable('course', false)) Boolean((utils.getQueryVariable('dev', false) or me.isTeacher()) and utils.getQueryVariable('course', false))
setRole: (role, force=false) -> setRole: (role, force=false) ->
return if me.isAdmin() return if me.isAdmin()

View file

@ -4,17 +4,26 @@ extends /templates/base
block content block content
h3 Classroom Levels
if !me.isAdmin() if !me.isAdmin()
div You must be logged in as an admin to view this page. div You must be logged in as an admin to view this page.
else else
p p
- var levelsTotal = 0; - var levelsTotal = 0;
table.table.table-striped.table-condensed
tr
th Levels
th Course
each course in view.courses.models each course in view.courses.models
- var campaign = view.campaigns.get(course.get('campaignID')); - var campaign = view.campaigns.get(course.get('campaignID'));
- var levels = campaign.getLevels().models; - var levels = campaign.getLevels().models;
- levelsTotal += levels.length; - levelsTotal += levels.length;
div #{levels.length} #{course.get('name')} tr
div #{levelsTotal} levels total td= levels.length
td= course.get('name')
tr
td= levelsTotal
td All
each course in view.courses.models each course in view.courses.models
- var campaign = view.campaigns.get(course.get('campaignID')); - var campaign = view.campaigns.get(course.get('campaignID'));
- var levels = campaign.getLevels().models; - var levels = campaign.getLevels().models;
@ -29,6 +38,7 @@ block content
th Type th Type
th Practice th Practice
th Practice Threshold (m) th Practice Threshold (m)
th Shareable
each level, levelIndex in levels each level, levelIndex in levels
- var levelNumber = campaign.getLevelNumber(level.get('original'), levelIndex + 1) - var levelNumber = campaign.getLevelNumber(level.get('original'), levelIndex + 1)
tr tr
@ -37,3 +47,4 @@ block content
td= level.get('type') td= level.get('type')
td= level.get('practice') td= level.get('practice')
td= level.get('practiceThresholdMinutes') td= level.get('practiceThresholdMinutes')
td= level.get('shareable')

View file

@ -122,10 +122,11 @@ block content
li(class=(activeTab === "#enrollment-status-tab" ? 'active' : '')) li(class=(activeTab === "#enrollment-status-tab" ? 'active' : ''))
a.course-progress-tab-btn(href='#enrollment-status-tab') a.course-progress-tab-btn(href='#enrollment-status-tab')
.small-details.text-center(data-i18n='teacher.enrollment_status') .small-details.text-center(data-i18n='teacher.enrollment_status')
//.tab-spacer if _.find(view.courses.models, function(c) { return /dev/.test(c.get('slug')); })
//li(class=(activeTab === "#student-projects-tab" ? 'active' : '')) .tab-spacer
// a.course-progress-tab-btn(href='#student-projects-tab') li(class=(activeTab === "#student-projects-tab" ? 'active' : ''))
// .small-details.text-center(data-i18n='teacher.projects') a.course-progress-tab-btn(href='#student-projects-tab')
.small-details.text-center(data-i18n='teacher.projects')
.tab-filler .tab-filler
.tab-content .tab-content

View file

@ -102,6 +102,11 @@ block header
a(data-toggle="coco-modal", data-target="editor/level/modals/GenerateTerrainModal", data-i18n="editor.generate_terrain", disabled=anonymous).generate-terrain-button Generate Terrain a(data-toggle="coco-modal", data-target="editor/level/modals/GenerateTerrainModal", data-i18n="editor.generate_terrain", disabled=anonymous).generate-terrain-button Generate Terrain
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
if view.courseID
li(class=anonymous ? "disabled": "")
a.play-classroom-level(data-classroom=true, data-code-language="javascript") Play Classroom JavaScript
li(class=anonymous ? "disabled": "")
a.play-classroom-level(data-classroom=true, data-code-language="python") Play Classroom Python
li.divider li.divider
li.dropdown-header(data-i18n="common.info") Info li.dropdown-header(data-i18n="common.info") Info
li#level-history-button li#level-history-button

View file

@ -6,6 +6,10 @@ World = require 'lib/world/world'
DocumentFiles = require 'collections/DocumentFiles' DocumentFiles = require 'collections/DocumentFiles'
LevelLoader = require 'lib/LevelLoader' LevelLoader = require 'lib/LevelLoader'
Campaigns = require 'collections/Campaigns'
CocoCollection = require 'collections/CocoCollection'
Course = require 'models/Course'
# in the template, but need to require them to load them # in the template, but need to require them to load them
require 'views/modal/RevertModal' require 'views/modal/RevertModal'
require 'views/editor/level/modals/GenerateTerrainModal' require 'views/editor/level/modals/GenerateTerrainModal'
@ -49,6 +53,7 @@ module.exports = class LevelEditView extends RootView
'click #play-button': 'onPlayLevel' 'click #play-button': 'onPlayLevel'
'click .play-with-team-button': 'onPlayLevel' 'click .play-with-team-button': 'onPlayLevel'
'click .play-with-team-parent': 'onPlayLevelTeamSelect' 'click .play-with-team-parent': 'onPlayLevelTeamSelect'
'click .play-classroom-level': 'onPlayLevel'
'click #commit-level-start-button': 'startCommittingLevel' 'click #commit-level-start-button': 'startCommittingLevel'
'click li:not(.disabled) > #fork-start-button': 'startForking' 'click li:not(.disabled) > #fork-start-button': 'startForking'
'click #level-history-button': 'showVersionHistory' 'click #level-history-button': 'showVersionHistory'
@ -73,6 +78,10 @@ module.exports = class LevelEditView extends RootView
@level = @levelLoader.level @level = @levelLoader.level
@files = new DocumentFiles(@levelLoader.level) @files = new DocumentFiles(@levelLoader.level)
@supermodel.loadCollection(@files, 'file_names') @supermodel.loadCollection(@files, 'file_names')
@campaigns = new Campaigns()
@supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels' } })
@courses = new CocoCollection([], { url: "/db/course", model: Course})
@supermodel.loadCollection(@courses, 'courses')
destroy: -> destroy: ->
clearInterval @timerIntervalID clearInterval @timerIntervalID
@ -89,6 +98,12 @@ module.exports = class LevelEditView extends RootView
@world = @levelLoader.world @world = @levelLoader.world
@render() @render()
@timerIntervalID = setInterval @incrementBuildTime, 1000 @timerIntervalID = setInterval @incrementBuildTime, 1000
campaignCourseMap = {}
campaignCourseMap[course.get('campaignID')] = course.id for course in @courses.models
for campaign in @campaigns.models
for levelID, level of campaign.get('levels') when levelID is @level.get('original')
@courseID = campaignCourseMap[campaign.id]
break if @courseID
getRenderData: (context={}) -> getRenderData: (context={}) ->
context = super(context) context = super(context)
@ -130,9 +145,11 @@ module.exports = class LevelEditView extends RootView
onPlayLevel: (e) -> onPlayLevel: (e) ->
team = $(e.target).data('team') team = $(e.target).data('team')
opponentSessionID = $(e.target).data('opponent') opponentSessionID = $(e.target).data('opponent')
newClassMode = $(e.target).data('classroom')
newClassLanguage = $(e.target).data('code-language')
sendLevel = => sendLevel = =>
@childWindow.Backbone.Mediator.publish 'level:reload-from-data', level: @level, supermodel: @supermodel @childWindow.Backbone.Mediator.publish 'level:reload-from-data', level: @level, supermodel: @supermodel
if @childWindow and not @childWindow.closed if @childWindow and not @childWindow.closed and @playClassMode is newClassMode and @playClassLanguage is newClassLanguage
# Reset the LevelView's world, but leave the rest of the state alone # Reset the LevelView's world, but leave the rest of the state alone
sendLevel() sendLevel()
else else
@ -140,6 +157,11 @@ module.exports = class LevelEditView extends RootView
scratchLevelID = @level.get('slug') + '?dev=true' scratchLevelID = @level.get('slug') + '?dev=true'
scratchLevelID += "&team=#{team}" if team scratchLevelID += "&team=#{team}" if team
scratchLevelID += "&opponent=#{opponentSessionID}" if opponentSessionID scratchLevelID += "&opponent=#{opponentSessionID}" if opponentSessionID
@playClassMode = newClassMode
@playClassLanguage = newClassLanguage
if @playClassMode
scratchLevelID += "&course=#{@courseID}"
scratchLevelID += "&codeLanguage=#{@playClassLanguage}"
if me.get('name') is 'Nick' if me.get('name') is 'Nick'
@childWindow = window.open("/play/level/#{scratchLevelID}", 'child_window', 'width=2560,height=1080,left=0,top=-1600,location=1,menubar=1,scrollbars=1,status=0,titlebar=1,toolbar=1', true) @childWindow = window.open("/play/level/#{scratchLevelID}", 'child_window', 'width=2560,height=1080,left=0,top=-1600,location=1,menubar=1,scrollbars=1,status=0,titlebar=1,toolbar=1', true)
else else

View file

@ -131,7 +131,10 @@ module.exports = class SpellView extends CocoView
name: 'run-code-real-time' name: 'run-code-real-time'
bindKey: {win: 'Ctrl-Shift-Enter', mac: 'Command-Shift-Enter|Ctrl-Shift-Enter'} bindKey: {win: 'Ctrl-Shift-Enter', mac: 'Command-Shift-Enter|Ctrl-Shift-Enter'}
exec: => exec: =>
if @options.level.get('replayable') and (timeUntilResubmit = @session.timeUntilResubmit()) > 0 doneButton = @$('.done-button:visible')
if doneButton.length
doneButton.trigger 'click'
else if @options.level.get('replayable') and (timeUntilResubmit = @session.timeUntilResubmit()) > 0
Backbone.Mediator.publish 'tome:manual-cast-denied', timeUntilResubmit: timeUntilResubmit Backbone.Mediator.publish 'tome:manual-cast-denied', timeUntilResubmit: timeUntilResubmit
else else
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true} Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}

127
scripts/fixCloseIoOpps.js Normal file
View file

@ -0,0 +1,127 @@
// Fix Close.io opportunity owners
'use strict';
if (process.argv.length !== 8) {
log("Usage: node <script> <Close.io general API key> <Close.io mail API key1> <Close.io mail API key2> <Close.io mail API key3> <Close.io EU mail API key>");
process.exit();
}
const scriptStartTime = new Date();
const closeIoApiKey = process.argv[2];
const closeIoMailApiKeys = [process.argv[3], process.argv[4], process.argv[5], process.argv[6], process.argv[7]];
const async = require('async');
const request = require('request');
// ** Main program
getUsers((err, ownerId, userApiKeyMap) => {
if (err) {
console.error(err);
return;
}
getOpps(ownerId, (err, opps) => {
if (err) {
console.error(err);
return;
}
log(`${opps.length} opps owned by ${userApiKeyMap[ownerId].data.first_name}`);
const tasks = [];
for (const opp of opps) {
tasks.push(createUpdateOppFn(ownerId, userApiKeyMap, opp));
}
async.parallel(tasks, (err, results) => {
if (err) console.error(err);
log("Script runtime: " + (new Date() - scriptStartTime));
});
});
});
function getUsers(done) {
let ownerId = null;
const userApiKeyMap = {};
let createGetUserFn = (apiKey) => {
return (done) => {
const url = `https://${apiKey}:X@app.close.io/api/v1/me/`;
request.get(url, (error, response, body) => {
if (error) return done();
const results = JSON.parse(body);
userApiKeyMap[results.id] = {key: apiKey, data: results};
if (apiKey === closeIoApiKey) {
ownerId = results.id;
}
return done();
});
};
}
const tasks = [createGetUserFn(closeIoApiKey)];
for (const apiKey of closeIoMailApiKeys) {
tasks.push(createGetUserFn(apiKey));
}
async.parallel(tasks, (err, results) => {
if (err) return done(err);
return done(null, ownerId, userApiKeyMap);
});
}
function getOpps(ownerId, done) {
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/opportunity/?user_id=${ownerId}`;
request.get(url, (err, response, body) => {
if (err) return done(err);
const results = JSON.parse(body);
return done(null, results.data);
});
}
function createUpdateOppFn(ownerId, userApiKeyMap, opp) {
return (done) => {
findOwner(ownerId, userApiKeyMap, opp, (err, userId) => {
if (err) return done(err);
// console.log(`DEBUG: ${opp.lead_id} owner ${userApiKeyMap[userId].data.first_name}`);
return updateOpp(opp, userId, userApiKeyMap, done);
});
};
}
function findOwner(ownerId, userApiKeyMap, opp, done) {
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/activity/?lead_id=${opp.lead_id}`;
request.get(url, (err, response, body) => {
if (err) return done(err);
const results = JSON.parse(body);
if (results.has_more) {
console.log(`ERROR: ${lead.id} has more activities than ${results.data.length} returned!`);
}
for (const activity of results.data) {
if (activity._type === 'Email' && userApiKeyMap[activity.user_id] && activity.user_id !== ownerId) {
return done(null, activity.user_id);
}
}
return done(`ERROR: No owner found for ${opp.lead_id}`);
});
}
function updateOpp(opp, userId, userApiKeyMap, done) {
const putData = {
user_id: userId,
user_name: `${userApiKeyMap[userId].data.first_name} ${userApiKeyMap[userId].data.last_name}`
};
console.log(`DEBUG: updating ${opp.lead_id} ${opp.id} to ${putData.user_name}`);
const options = {
uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/opportunity/${opp.id}/`,
body: JSON.stringify(putData)
};
request.put(options, (err, response, body) => {
if (err) return done(err);
const result = JSON.parse(body);
if (result.errors || result['field-errors']) {
console.log(`PUT error for ${opp.lead_id} ${opp.id}`);
return done(result.errors || result['field-errors']);
}
return done();
});
}
// ** Utilities
function log(str) {
console.log(new Date().toISOString() + " " + str);
}

View file

@ -1,8 +1,8 @@
// Follow up on Close.io leads // Follow up on Close.io leads
'use strict'; 'use strict';
if (process.argv.length !== 8) { if (process.argv.length !== 7) {
log("Usage: node <script> <Close.io general API key> <Close.io mail API key1> <Close.io mail API key2> <Close.io mail API key3> <mongo connection Url>"); log("Usage: node <script> <Close.io general API key> <Close.io mail API key1> <Close.io mail API key2> <Close.io mail API key3>");
process.exit(); process.exit();
} }
@ -12,17 +12,20 @@ if (process.argv.length !== 8) {
// TODO: sendMail copied from updateCloseIoLeads.js // TODO: sendMail copied from updateCloseIoLeads.js
// TODO: template values copied from updateCloseIoLeads.js // TODO: template values copied from updateCloseIoLeads.js
// TODO: status change is not related to specific lead contacts // TODO: status change is not related to specific lead contacts
// TODO: update status after adding a call task
const createTeacherEmailTemplatesAuto1 = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G']; const createTeacherEmailTemplatesAuto1 = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G'];
const demoRequestEmailTemplatesAuto1 = ['tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm', 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf']; const demoRequestEmailTemplatesAuto1 = ['tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm', 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf'];
const createTeacherInternationalEmailTemplateAuto1 = 'tmpl_8vsXwcr6dWefMnAEfPEcdHaxqSfUKUY8UKq6WfReGqG';
const demoRequestInternationalEmailTemplateAuto1 = 'tmpl_nnH1p3II7G7NJYiPOIHphuj4XUaDptrZk1mGQb2d9Xa';
const createTeacherEmailTemplatesAuto2 = ['tmpl_pGPtKa07ioISupdSc1MAzNC57K40XoA4k0PI1igi8Ec', 'tmpl_AYAcviU8NQGLbMGKSp3EmcBLha0gQw4cHSOR55Fmoha']; const createTeacherEmailTemplatesAuto2 = ['tmpl_pGPtKa07ioISupdSc1MAzNC57K40XoA4k0PI1igi8Ec', 'tmpl_AYAcviU8NQGLbMGKSp3EmcBLha0gQw4cHSOR55Fmoha'];
const demoRequestEmailTemplatesAuto2 = ['tmpl_HJ5zebh1SqC1QydDto05VPUMu4F7i5M35Llq7bzgfTw', 'tmpl_dmnK7IVpkyYfPYAl1rChhm9lClH5lJ9pQAZoPr7cvLt']; const demoRequestEmailTemplatesAuto2 = ['tmpl_HJ5zebh1SqC1QydDto05VPUMu4F7i5M35Llq7bzgfTw', 'tmpl_dmnK7IVpkyYfPYAl1rChhm9lClH5lJ9pQAZoPr7cvLt'];
const createTeacherInternationalEmailTemplatesAuto2 = ['tmpl_a6Syzzy6ri9MErfXQySM5UfaF5iNIv1VCArYowAEICT', 'tmpl_jOqWLgT0G19Eqs7qZaAeNwtiull7UrSX4ZuvkYRM2gC'];
const demoRequestInternationalEmailTemplatesAuto2 = ['tmpl_wz4SnDZMjNmAhp3MIuZaSMmjJTy5IW75Rcy3MYGb6Ti', 'tmpl_5oJ0YQMZFqNi3DgW7hplD6JS2zHqkB4Gt7Fj1u19Nks'];
const scriptStartTime = new Date(); const scriptStartTime = new Date();
const closeIoApiKey = process.argv[2]; const closeIoApiKey = process.argv[2];
const closeIoMailApiKeys = [process.argv[3], process.argv[4], process.argv[5], process.argv[6]]; // Automatic mails sent as API owners const closeIoMailApiKeys = [process.argv[3], process.argv[4], process.argv[5], process.argv[6]]; // Automatic mails sent as API owners
const mongoConnUrl = process.argv[7];
const MongoClient = require('mongodb').MongoClient;
const async = require('async'); const async = require('async');
const request = require('request'); const request = require('request');
@ -39,13 +42,28 @@ async.series([
(err, results) => { (err, results) => {
if (err) console.error(err); if (err) console.error(err);
log("Script runtime: " + (new Date() - scriptStartTime)); log("Script runtime: " + (new Date() - scriptStartTime));
} });
);
// ** Utilities // ** Utilities
function getRandomEmailTemplateAuto2(template) {
if (createTeacherEmailTemplatesAuto1.indexOf(template) >= 0) {
return getRandomEmailTemplate(createTeacherEmailTemplatesAuto2);
}
if (demoRequestEmailTemplatesAuto1.indexOf(template) >= 0) {
return getRandomEmailTemplate(demoRequestEmailTemplatesAuto2);
}
if (createTeacherInternationalEmailTemplateAuto1 == template) {
return getRandomEmailTemplate(createTeacherInternationalEmailTemplatesAuto2);
}
if (demoRequestInternationalEmailTemplateAuto1 === template) {
return getRandomEmailTemplate(demoRequestInternationalEmailTemplatesAuto2);
}
return null;
}
function getRandomEmailTemplate(templates) { function getRandomEmailTemplate(templates) {
if (templates.length < 0) return ''; if (templates.length < 0) return null;
return templates[Math.floor(Math.random() * templates.length)]; return templates[Math.floor(Math.random() * templates.length)];
} }
@ -59,20 +77,20 @@ function isSameEmailTemplateType(template1, template2) {
return false; return false;
} }
function isDemoRequestTemplateAuto1(template) { function isTemplateAuto1(template) {
return demoRequestEmailTemplatesAuto1.indexOf(template) >= 0; if (createTeacherEmailTemplatesAuto1.indexOf(template) >= 0) return true;
if (demoRequestEmailTemplatesAuto1.indexOf(template) >= 0) return true;
if (createTeacherInternationalEmailTemplateAuto1 == template) return true;
if (demoRequestInternationalEmailTemplateAuto1 === template) return true;
return false;
} }
function isCreateTeacherTemplateAuto1(template) { function isTemplateAuto2(template) {
return createTeacherEmailTemplatesAuto1.indexOf(template) >= 0; if (createTeacherEmailTemplatesAuto2.indexOf(template) >= 0) return true;
} if (demoRequestEmailTemplatesAuto2.indexOf(template) >= 0) return true;
if (createTeacherInternationalEmailTemplatesAuto2.indexOf(template) >= 0) return true;
function isDemoRequestTemplateAuto2(template) { if (demoRequestInternationalEmailTemplatesAuto2.indexOf(template) >= 0) return true;
return demoRequestEmailTemplatesAuto2.indexOf(template) >= 0; return false;
}
function isCreateTeacherTemplateAuto2(template) {
return createTeacherEmailTemplatesAuto2.indexOf(template) >= 0;
} }
function log(str) { function log(str) {
@ -137,7 +155,7 @@ function sendMail(toEmail, leadId, contactId, template, emailApiKey, delayMinute
} }
function updateLeadStatus(lead, status, done) { function updateLeadStatus(lead, status, done) {
// console.log("DEBUG: updateLeadStatus", lead.id, status); // console.log(`DEBUG: updateLeadStatus ${lead.id} ${status}`);
const putData = {status: status}; const putData = {status: status};
const options = { const options = {
uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/${lead.id}/`, uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/${lead.id}/`,
@ -202,25 +220,14 @@ function createSendFollowupMailFn(userApiKeyMap, latestDate, lead, email) {
} }
// Find first auto mail // Find first auto mail
let sentFirstCreateTeacherEmail = false;
let sentFirstDemoRequestEmail = false;
let firstMailActivity; let firstMailActivity;
for (const activity of results.data) { for (const activity of results.data) {
if (activity._type === 'Email' && activity.to[0] === email) { if (activity._type === 'Email' && activity.to[0] === email) {
if (isCreateTeacherTemplateAuto1(activity.template_id)) { if (isTemplateAuto1(activity.template_id)) {
if (sentFirstCreateTeacherEmail || sentFirstDemoRequestEmail) { if (firstMailActivity) {
console.log(`ERROR: ${lead.id} sent multiple auto1 emails!? ${sentFirstCreateTeacherEmail} ${sentFirstDemoRequestEmail}`); console.log(`ERROR: ${lead.id} sent multiple auto1 emails!?`);
return done(); return done();
} }
sentFirstCreateTeacherEmail = true;
firstMailActivity = activity;
}
else if (isDemoRequestTemplateAuto1(activity.template_id)) {
if (sentFirstCreateTeacherEmail || sentFirstDemoRequestEmail) {
console.log(`ERROR: ${lead.id} sent multiple auto1 emails!? ${sentFirstCreateTeacherEmail} ${sentFirstDemoRequestEmail}`);
return done();
}
sentFirstDemoRequestEmail = true;
firstMailActivity = activity; firstMailActivity = activity;
} }
} }
@ -235,11 +242,6 @@ function createSendFollowupMailFn(userApiKeyMap, latestDate, lead, email) {
return done(); return done();
} }
if (sentFirstCreateTeacherEmail && sentFirstDemoRequestEmail) {
console.log(`ERROR: ${lead.id} sent multiple auto1 emails!? ${sentFirstCreateTeacherEmail} ${sentFirstDemoRequestEmail}`);
return done();
}
// Find activity since first auto mail, that's not email to a different contact's email // Find activity since first auto mail, that's not email to a different contact's email
let recentActivity; let recentActivity;
for (const activity of results.data) { for (const activity of results.data) {
@ -251,17 +253,9 @@ function createSendFollowupMailFn(userApiKeyMap, latestDate, lead, email) {
} }
if (!recentActivity) { if (!recentActivity) {
let template; let template = getRandomEmailTemplateAuto2(firstMailActivity.template_id);
if (sentFirstCreateTeacherEmail) {
// console.log(`Create teacher auto 1 sent: ${lead.id} ${firstMailUserId} ${userApiKeyMap[firstMailUserId]}`);
template = getRandomEmailTemplate(createTeacherEmailTemplatesAuto2);
}
else if (sentFirstDemoRequestEmail) {
// console.log(`Demo request auto 1 sent: ${lead.id} ${firstMailUserId} ${userApiKeyMap[firstMailUserId]}`);
template = getRandomEmailTemplate(demoRequestEmailTemplatesAuto2);
}
if (!template) { if (!template) {
console.log(`ERROR: no template selected ${lead.id}`); console.log(`ERROR: no auto2 template selected for ${lead.id} ${firstMailActivity.template_id}`);
return done(); return done();
} }
// console.log(`TODO: ${firstMailActivity.to[0]} ${lead.id} ${firstMailActivity.contact_id} ${template} ${userApiKeyMap[firstMailActivity.user_id]}`); // console.log(`TODO: ${firstMailActivity.to[0]} ${lead.id} ${firstMailActivity.contact_id} ${template} ${userApiKeyMap[firstMailActivity.user_id]}`);
@ -271,12 +265,25 @@ function createSendFollowupMailFn(userApiKeyMap, latestDate, lead, email) {
// TODO: some sort of callback problem that stops the series here // TODO: some sort of callback problem that stops the series here
// TODO: manage this status mapping better
if (lead.status_label === "Auto Attempt 1") { if (lead.status_label === "Auto Attempt 1") {
return updateLeadStatus(lead, "Auto Attempt 2", done); return updateLeadStatus(lead, "Auto Attempt 2", done);
} }
else if (lead.status_label === "New US Schools Auto Attempt 1") { else if (lead.status_label === "New US Schools Auto Attempt 1") {
return updateLeadStatus(lead, "New US Schools Auto Attempt 2", done); return updateLeadStatus(lead, "New US Schools Auto Attempt 2", done);
} }
else if (lead.status_label === "Inbound AU Auto Attempt 1") {
return updateLeadStatus(lead, "Inbound AU Auto Attempt 2", done);
}
else if (lead.status_label === "Inbound Canada Auto Attempt 1") {
return updateLeadStatus(lead, "Inbound Canada Auto Attempt 2", done);
}
else if (lead.status_label === "Inbound NZ Auto Attempt 1") {
return updateLeadStatus(lead, "Inbound NZ Auto Attempt 2", done);
}
else if (lead.status_label === "Inbound UK Auto Attempt 1") {
return updateLeadStatus(lead, "Inbound UK Auto Attempt 2", done);
}
else { else {
console.log(`ERROR: unknown lead status ${lead.id} ${lead.status_label}`) console.log(`ERROR: unknown lead status ${lead.id} ${lead.status_label}`)
return done(); return done();
@ -322,7 +329,8 @@ function sendSecondFollowupMails(done) {
if (err) console.log(err); if (err) console.log(err);
const latestDate = new Date(); const latestDate = new Date();
latestDate.setUTCDate(latestDate.getUTCDate() - 3); latestDate.setUTCDate(latestDate.getUTCDate() - 3);
const query = `date_created > ${earliestDate.toISOString().substring(0, 19)} (lead_status:"Auto Attempt 1" or lead_status:"New US Schools Auto Attempt 1")"`; // TODO: manage this status list better
const query = `date_created > ${earliestDate.toISOString().substring(0, 19)} (lead_status:"Auto Attempt 1" or lead_status:"New US Schools Auto Attempt 1" or lead_status:"Inbound Canada Auto Attempt 1" or lead_status:"Inbound AU Auto Attempt 1" or lead_status:"Inbound NZ Auto Attempt 1" or lead_status:"Inbound UK Auto Attempt 1")`;
const limit = 100; const limit = 100;
const nextPage = (skip) => { const nextPage = (skip) => {
let has_more = false; let has_more = false;
@ -337,15 +345,15 @@ function sendSecondFollowupMails(done) {
has_more = results.has_more; has_more = results.has_more;
const tasks = []; const tasks = [];
for (const lead of results.data) { for (const lead of results.data) {
// console.log(`${lead.id}\t${lead.status_label}\t${lead.name}`); // console.log(`DEBUG: ${lead.id}\t${lead.status_label}\t${lead.name}`);
// if (lead.id !== 'lead_KYuI2HVOiUdJANvkOe1uLJBuuQVaaGSRveklhTWbHv2') continue; // if (lead.id !== 'lead_8YZlEVQ4w3lETSlF43RQHK7cJQaBQ4tpbbxUUNA2uGC') continue;
const existingContacts = lead.contacts || []; const existingContacts = lead.contacts || [];
for (const contact of existingContacts) { for (const contact of existingContacts) {
if (contact.emails && contact.emails.length > 0) { if (contact.emails && contact.emails.length > 0) {
tasks.push(createSendFollowupMailFn(userApiKeyMap, latestDate, lead, contact.emails[0].email.toLowerCase())); tasks.push(createSendFollowupMailFn(userApiKeyMap, latestDate, lead, contact.emails[0].email.toLowerCase()));
} }
else { else {
console.log(`ERROR: lead ${lead.id} contact has non-1 emails`); console.log(`ERROR: lead ${lead.id} contact ${contact.id} has no email`);
} }
} }
} }
@ -370,8 +378,8 @@ function createAddCallTaskFn(userApiKeyMap, latestDate, lead, email) {
// Check for activity since second auto mail and status update // Check for activity since second auto mail and status update
// Add call task // Add call task
// TODO: Very similar function to createSendFollowupMailFn // TODO: Very similar function to createSendFollowupMailFn
const auto1Statuses = ["Auto Attempt 1", "New US Schools Auto Attempt 1"]; const auto1Statuses = ["Auto Attempt 1", "New US Schools Auto Attempt 1", "Inbound Canada Auto Attempt 1", "Inbound AU Auto Attempt 1", "Inbound NZ Auto Attempt 1", "Inbound UK Auto Attempt 1"];
const auto2Statuses = ["Auto Attempt 2", "New US Schools Auto Attempt 2"]; const auto2Statuses = ["Auto Attempt 2", "New US Schools Auto Attempt 2", "Inbound Canada Auto Attempt 2", "Inbound AU Auto Attempt 2", "Inbound NZ Auto Attempt 2", "Inbound UK Auto Attempt 2"];
return (done) => { return (done) => {
// console.log("DEBUG: addCallTask", lead.id); // console.log("DEBUG: addCallTask", lead.id);
@ -408,27 +416,15 @@ function createAddCallTaskFn(userApiKeyMap, latestDate, lead, email) {
} }
// Find second auto mail and status change // Find second auto mail and status change
let sentSecondCreateTeacherEmail = false;
let sentSecondDemoRequestEmail = false;
let secondMailActivity; let secondMailActivity;
let statusUpdateActivity; let statusUpdateActivity;
let contactReplyMail;
for (const activity of results.data) { for (const activity of results.data) {
if (activity._type === 'Email' && activity.to[0] === email) { if (activity._type === 'Email' && activity.to[0] === email) {
if (isCreateTeacherTemplateAuto2(activity.template_id)) { if (isTemplateAuto2(activity.template_id)) {
if (sentSecondCreateTeacherEmail || sentSecondDemoRequestEmail) { if (secondMailActivity) {
console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`); console.log(`ERROR: ${lead.id} sent multiple auto2 emails!?`);
return done(); return done();
} }
sentSecondCreateTeacherEmail = true;
secondMailActivity = activity;
}
else if (isDemoRequestTemplateAuto2(activity.template_id)) {
if (sentSecondCreateTeacherEmail || sentSecondDemoRequestEmail) {
console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`);
return done();
}
sentSecondDemoRequestEmail = true;
secondMailActivity = activity; secondMailActivity = activity;
} }
} }
@ -451,12 +447,6 @@ function createAddCallTaskFn(userApiKeyMap, latestDate, lead, email) {
return done(); return done();
} }
if (sentSecondCreateTeacherEmail && sentSecondDemoRequestEmail) {
console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`);
return done();
}
// console.log(secondMailActivity);
// Find activity since second auto mail and status update // Find activity since second auto mail and status update
// Skip email to a different contact's email // Skip email to a different contact's email
// Skip note about different contact // Skip note about different contact
@ -511,7 +501,7 @@ function createAddCallTaskFn(userApiKeyMap, latestDate, lead, email) {
} }
catch (err) { catch (err) {
console.log(err); console.log(err);
console.log(body); // console.log(body);
return done(); return done();
} }
}); });
@ -543,7 +533,7 @@ function addCallTasks(done) {
if (err) console.log(err); if (err) console.log(err);
const latestDate = new Date(); const latestDate = new Date();
latestDate.setUTCDate(latestDate.getUTCDate() - 3); latestDate.setUTCDate(latestDate.getUTCDate() - 3);
const query = `date_created > ${earliestDate.toISOString().substring(0, 19)} (lead_status:"Auto Attempt 2" or lead_status:"New US Schools Auto Attempt 2")"`; const query = `date_created > ${earliestDate.toISOString().substring(0, 19)} (lead_status:"Auto Attempt 2" or lead_status:"New US Schools Auto Attempt 2" or lead_status:"Inbound Canada Auto Attempt 2" or lead_status:"Inbound AU Auto Attempt 2" or lead_status:"Inbound NZ Auto Attempt 2" or lead_status:"Inbound UK Auto Attempt 2")`;
const limit = 100; const limit = 100;
const nextPage = (skip) => { const nextPage = (skip) => {
let has_more = false; let has_more = false;
@ -568,10 +558,10 @@ function addCallTasks(done) {
} }
} }
else { else {
console.log(`ERROR: lead ${lead.id} contact has non-1 emails`); console.log(`ERROR: lead ${lead.id} contact ${contact.id} has no email`);
} }
} }
// if (tasks.length > 10) break; // if (tasks.length > 1) break;
} }
async.series(tasks, (err, results) => { async.series(tasks, (err, results) => {
if (err) return done(err); if (err) return done(err);

View file

@ -9,6 +9,7 @@ describe 'UserModel', ->
expect(User.expForLevel 2).toBeGreaterThan User.expForLevel 1 expect(User.expForLevel 2).toBeGreaterThan User.expForLevel 1
it 'level is calculated correctly', -> it 'level is calculated correctly', ->
me.clear()
me.set 'points', 0 me.set 'points', 0
expect(me.level()).toBe 1 expect(me.level()).toBe 1