mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 08:41:46 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
d1206ba692
11 changed files with 356 additions and 193 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
127
scripts/fixCloseIoOpps.js
Normal 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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue