mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-13 01:01:34 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
4419f80d4c
15 changed files with 236 additions and 83 deletions
|
@ -30,6 +30,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
|
|
||||||
'admin': go('admin/MainAdminView')
|
'admin': go('admin/MainAdminView')
|
||||||
'admin/clas': go('admin/CLAsView')
|
'admin/clas': go('admin/CLAsView')
|
||||||
|
'admin/classroom-levels': go('admin/AdminClassroomLevelsView')
|
||||||
'admin/design-elements': go('admin/DesignElementsView')
|
'admin/design-elements': go('admin/DesignElementsView')
|
||||||
'admin/files': go('admin/FilesView')
|
'admin/files': go('admin/FilesView')
|
||||||
'admin/analytics': go('admin/AnalyticsView')
|
'admin/analytics': go('admin/AnalyticsView')
|
||||||
|
|
|
@ -342,31 +342,31 @@ module.exports.createLevelNumberMap = (levels) ->
|
||||||
levelNumberMap
|
levelNumberMap
|
||||||
|
|
||||||
module.exports.findNextLevel = (levels, currentIndex, needsPractice) ->
|
module.exports.findNextLevel = (levels, currentIndex, needsPractice) ->
|
||||||
# levels = [{practice: true/false, complete: true/false}]
|
# levels = [{practice: true/false, complete: true/false}]
|
||||||
index = currentIndex
|
index = currentIndex
|
||||||
index++
|
index++
|
||||||
if needsPractice
|
if needsPractice
|
||||||
if levels[currentIndex].practice or index < levels.length and levels[index].practice
|
if levels[currentIndex].practice or index < levels.length and levels[index].practice
|
||||||
# Needs practice, on practice or next practice, choose next incomplete level
|
# Needs practice, on practice or next practice, choose next incomplete level
|
||||||
# May leave earlier practice levels incomplete and reach end of course
|
# May leave earlier practice levels incomplete and reach end of course
|
||||||
index++ while index < levels.length and levels[index].complete
|
index++ while index < levels.length and levels[index].complete
|
||||||
else
|
|
||||||
# Needs practice, on required, next required, choose first incomplete level of previous practice chain
|
|
||||||
index--
|
|
||||||
index-- while index >= 0 and not levels[index].practice
|
|
||||||
if index >= 0
|
|
||||||
index-- while index >= 0 and levels[index].practice
|
|
||||||
if index >= 0
|
|
||||||
index++
|
|
||||||
index++ while index < levels.length and levels[index].practice and levels[index].complete
|
|
||||||
if levels[index].practice and not levels[index].complete
|
|
||||||
return index
|
|
||||||
index = currentIndex + 1
|
|
||||||
index++ while index < levels.length and levels[index].complete
|
|
||||||
else
|
else
|
||||||
# No practice needed, next required incomplete level
|
# Needs practice, on required, next required, choose first incomplete level of previous practice chain
|
||||||
index++ while index < levels.length and (levels[index].practice or levels[index].complete)
|
index--
|
||||||
index
|
index-- while index >= 0 and not levels[index].practice
|
||||||
|
if index >= 0
|
||||||
|
index-- while index >= 0 and levels[index].practice
|
||||||
|
if index >= 0
|
||||||
|
index++
|
||||||
|
index++ while index < levels.length and levels[index].practice and levels[index].complete
|
||||||
|
if levels[index].practice and not levels[index].complete
|
||||||
|
return index
|
||||||
|
index = currentIndex + 1
|
||||||
|
index++ while index < levels.length and levels[index].complete
|
||||||
|
else
|
||||||
|
# No practice needed, next required incomplete level
|
||||||
|
index++ while index < levels.length and (levels[index].practice or levels[index].complete)
|
||||||
|
index
|
||||||
|
|
||||||
module.exports.needsPractice = (playtime=0, threshold=2) ->
|
module.exports.needsPractice = (playtime=0, threshold=2) ->
|
||||||
playtime / 60 > threshold
|
playtime / 60 > threshold
|
||||||
|
|
|
@ -15,7 +15,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
||||||
or_ipad: "Или скачайте на iPad"
|
or_ipad: "Или скачайте на iPad"
|
||||||
|
|
||||||
new_home:
|
new_home:
|
||||||
slogan: "Самая захватывающая игра для обучения программированию"
|
slogan: "Самая захватывающая игра для обучения программированию."
|
||||||
classroom_edition: "Классная комната:"
|
classroom_edition: "Классная комната:"
|
||||||
learn_to_code: "Учись программировать:"
|
learn_to_code: "Учись программировать:"
|
||||||
teacher: "Учитель"
|
teacher: "Учитель"
|
||||||
|
@ -24,44 +24,44 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
||||||
im_a_teacher: "Я учитель"
|
im_a_teacher: "Я учитель"
|
||||||
im_a_student: "Я ученик"
|
im_a_student: "Я ученик"
|
||||||
learn_more: "Узнать больше"
|
learn_more: "Узнать больше"
|
||||||
# classroom_in_a_box: "A classroom in-a-box for teaching computer science."
|
classroom_in_a_box: "Готовая учебный кабинет из коробки для обучения информатике."
|
||||||
codecombat_is: "CodeCombat это платформа <strong>для студентов</strong> чтобы изучать компьтерные науки во время игры."
|
codecombat_is: "CodeCombat это платформа <strong>для студентов</strong> чтобы изучать информатику во время игры."
|
||||||
# our_courses: "Our courses have been specifically playtested to <strong>excel in the classroom</strong>, even by teachers with little to no prior programming experience."
|
our_courses: "Наши курсы были тщательно проработаны чтобы <strong>качественно обучать</strong>, даже если учителя не имееют особого опыта в программировании."
|
||||||
top_screenshots_hint: "Студенты пишут код и видят как их изменения обновляются в реальном времени"
|
top_screenshots_hint: "Студенты пишут код и видят как их изменения обновляются в реальном времени"
|
||||||
designed_with: "Разработанный при поддержке учителей"
|
designed_with: "Разработано при поддержке учителей"
|
||||||
real_code: "По настоящему писать код"
|
real_code: "По настоящему писать код"
|
||||||
from_the_first_level: "с первого уровня"
|
from_the_first_level: "с первого уровня"
|
||||||
getting_students: "Студенты получают возможность писать код как можно скорее это важно для обучения синтаксиса программирования и правильной структуры."
|
getting_students: "Студенты получают возможность писать код как можно скорее. Это важно для обучения синтаксиса языков программирования и правильной структуры."
|
||||||
educator_resources: "Ресурсы педагога"
|
educator_resources: "Материалы для педагогов"
|
||||||
course_guides: "и руководство по курсам"
|
course_guides: "и руководство по курсам"
|
||||||
teaching_computer_science: "Обучение компьютерным наукам не требует высокой степени, потому что мы предоставляем инстументы для поддержки педагогов с любым уровнем подготовки."
|
teaching_computer_science: "Обучение информатике не требует высокой степени, так как мы предоставляем инструменты для поддержки педагогов с любым уровнем подготовки."
|
||||||
accessible_to: "Доступно для"
|
accessible_to: "Доступно для"
|
||||||
everyone: "каждого"
|
everyone: "каждого"
|
||||||
democratizing: "Демократизация процесса обучения программированию лежит в основе нашей философии. Каждый должен иметь возможность обучаться программированию."
|
democratizing: "Демократизация процесса обучения программированию лежит в основе нашей философии. Каждый должен иметь возможность обучаться программированию."
|
||||||
forgot_learning: "Я думаю, что они забудут, что они действительно что-то изучают."
|
forgot_learning: "Я думаю, что они забудут, что они действительно что-то изучают."
|
||||||
# wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school."
|
wanted_to_do: " Программирование - это то, чем я всегда хотел заниматься и никогда не подумал бы, что смогу изучать это в школе."
|
||||||
# why_games: "Why is learning through games important?"
|
why_games: "Почему обучение с помощью игры важно?"
|
||||||
# games_reward: "Games reward the productive struggle."
|
games_reward: "Игры поощряют эффективное упорство."
|
||||||
# encourage: "Gaming is a medium that encourages interaction, discovery, and trial-and-error. A good game challenges the player to master skills over time, which is the same critical process students go through as they learn."
|
encourage: "Игра - это процесс, который поощряет взаимодействие, исследования и метод проб и ошибок. Хорошие испытания в игре заставляют игрока постоянно улучшать свои навыки, что также очень важно для в процессе обучения."
|
||||||
# excel: "Games excel at rewarding"
|
excel: "Игры особенно хороши в поощрении"
|
||||||
# struggle: "productive struggle"
|
struggle: "эффективного упорства"
|
||||||
# kind_of_struggle: "the kind of struggle that results in learning that’s engaging and"
|
kind_of_struggle: "- это то упорство, достигаемое обучением, которое увлекательное и "
|
||||||
# motivating: "motivating"
|
motivating: "мотивирующее"
|
||||||
# not_tedious: "not tedious."
|
not_tedious: "а не скучное."
|
||||||
# gaming_is_good: "Studies suggest gaming is good for children’s brains. (it’s true!)"
|
gaming_is_good: "Исследования считают, что игры полезны для детских умов. (и это так и есть!)"
|
||||||
# game_based: "When game-based learning systems are"
|
game_based: "Когда игровая система обучения"
|
||||||
# compared: "compared"
|
compared: "сравнивается"
|
||||||
# conventional: "against conventional assessment methods, the difference is clear: games are better at helping students retain knowledge, concentrate and"
|
conventional: "с традиционными оценочными методами, то разница очевидна: игры лучше помогают ученикам получать знания, концетрироваться и"
|
||||||
# perform_at_higher_level: "perform at a higher level of achievement"
|
perform_at_higher_level: "достигать высоких результатов."
|
||||||
# feedback: "Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers."
|
feedback: "Также игры дают обратную связь в реальном времени, что позволяет ученикам быть гибкими в поисках решений и понимать концепции более глубоко, а не быть ограничеными результами “правильно” или “неправильно”."
|
||||||
# real_game: "A real game, played with real coding."
|
real_game: "Настоящая игра в которую играешь с помощью настоящего программирования."
|
||||||
# great_game: "A great game is more than just badges and achievements - it’s about a player’s journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence."
|
great_game: "Отличная игра - это больше чем бейджики и достижения. Это приключение для игрока, качественно сделаные головоломки и возможность преодолевать проблемы с уверенностью."
|
||||||
# agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code."
|
agency: "CodeCombat - это игра дающая с помощью надежной программной платформы, игрокам уверенность в результате, Что помогает как начинающим, так и опытным ученикам писать надежные, качественные программы."
|
||||||
# request_demo_title: "Get your students started today!"
|
request_demo_title: "Начните учить ваших учеников уже сегодня!"
|
||||||
# request_demo_subtitle: "Request a demo and get your students started in less than an hour."
|
request_demo_subtitle: "Запросите демо версию и начинайте обучение меньше чем через час."
|
||||||
# get_started_title: "Set up your class today"
|
get_started_title: "Подготовьте ваш класс уже сегодня"
|
||||||
# get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science."
|
get_started_subtitle: "Подготовьте класс, добавьте учеников, и наблюдайте за их прогрессом в обучении информатики."
|
||||||
request_demo: "Запрос Демонстрации"
|
request_demo: "Запросить демо версию"
|
||||||
setup_a_class: "Настройка класса"
|
setup_a_class: "Настройка класса"
|
||||||
have_an_account: "Уже есть аккаунт?"
|
have_an_account: "Уже есть аккаунт?"
|
||||||
logged_in_as: "Вы вошли как"
|
logged_in_as: "Вы вошли как"
|
||||||
|
@ -72,13 +72,13 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
||||||
ffa: "Бесплатно для всех студентов"
|
ffa: "Бесплатно для всех студентов"
|
||||||
lesson_time: "Время урока:"
|
lesson_time: "Время урока:"
|
||||||
coming_soon: "Скоро!"
|
coming_soon: "Скоро!"
|
||||||
# courses_available_in: "Courses are available in JavaScript, Python, and Java (coming soon!)"
|
courses_available_in: "Курсы доступны на JavaScript, Python, и Java (скоро!)"
|
||||||
# boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike."
|
boast: "Похвальные задачи, которые сложны в меру, чтобы очаровать как игроков, так и программистов."
|
||||||
# winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
|
winning: "Выигрышная комбинация ролевой игры и домашнего задания по программированию у которой получилось сделать обучение правильным, захватывающим и дружественным к детям."
|
||||||
# run_class: "Everything you need to run a computer science class in your school today, no CS background required."
|
run_class: "Все что вам нужно, чтобы начать класс информатики в вашей школе уже сегодня без подготовки по курсу информатики."
|
||||||
teachers: "Учителя!"
|
teachers: "Учителя!"
|
||||||
teachers_and_educators: "Учителя & Педагоги"
|
teachers_and_educators: "Учителя & Педагоги"
|
||||||
class_in_box: "Узнайте, как наш класс в коробке вписывается в ваш учебный план."
|
class_in_box: "Узнайте, как наш концепция готового класса из коробки вписывается в ваш учебный план."
|
||||||
get_started: "Начать"
|
get_started: "Начать"
|
||||||
students: "Студенты:"
|
students: "Студенты:"
|
||||||
join_class: "Присоединиться к классу"
|
join_class: "Присоединиться к классу"
|
||||||
|
@ -89,9 +89,9 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
||||||
goto_classes: "Перейти в Мои классы"
|
goto_classes: "Перейти в Мои классы"
|
||||||
view_profile: "Посмотреть Мой профиль"
|
view_profile: "Посмотреть Мой профиль"
|
||||||
view_progress: "Посмотреть прогресс"
|
view_progress: "Посмотреть прогресс"
|
||||||
check_out_wiki: "Посетите наш новый Wiki педагога"
|
check_out_wiki: "Посетите наш новый Wiki для педагогов"
|
||||||
want_coco: "Хотите CodeCombat в вашей школе?"
|
want_coco: "Хотите CodeCombat в вашей школе?"
|
||||||
form_select_role: "Выберите главную роль"
|
form_select_role: "Выберите основную роль"
|
||||||
form_select_range: "Выберите размер класса"
|
form_select_range: "Выберите размер класса"
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
|
|
5
app/styles/admin/admin-classroom-levels.sass
Normal file
5
app/styles/admin/admin-classroom-levels.sass
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#admin-classroom-levels-view
|
||||||
|
|
||||||
|
table
|
||||||
|
td, th
|
||||||
|
padding: 0px
|
|
@ -20,5 +20,9 @@
|
||||||
.test-failed
|
.test-failed
|
||||||
color: red
|
color: red
|
||||||
|
|
||||||
|
.solution
|
||||||
|
max-height: 200px
|
||||||
|
overflow: auto
|
||||||
|
|
||||||
.lineUnder
|
.lineUnder
|
||||||
border-bottom: 1px solid #ccc
|
border-bottom: 1px solid #ccc
|
|
@ -42,6 +42,8 @@ block content
|
||||||
if me.isAdmin()
|
if me.isAdmin()
|
||||||
h4 Analytics
|
h4 Analytics
|
||||||
ul
|
ul
|
||||||
|
li
|
||||||
|
a(href="/admin/classroom-levels") Classroom Levels
|
||||||
li
|
li
|
||||||
a(href="/admin/analytics") Dashboard
|
a(href="/admin/analytics") Dashboard
|
||||||
li
|
li
|
||||||
|
|
39
app/templates/admin/admin-classroom-levels.jade
Normal file
39
app/templates/admin/admin-classroom-levels.jade
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
extends /templates/base
|
||||||
|
|
||||||
|
//- DO NOT TRANSLATE
|
||||||
|
|
||||||
|
block content
|
||||||
|
|
||||||
|
if !me.isAdmin()
|
||||||
|
div You must be logged in as an admin to view this page.
|
||||||
|
else
|
||||||
|
p
|
||||||
|
- var levelsTotal = 0;
|
||||||
|
each course in view.courses.models
|
||||||
|
- var campaign = view.campaigns.get(course.get('campaignID'));
|
||||||
|
- var levels = campaign.getLevels().models;
|
||||||
|
- levelsTotal += levels.length;
|
||||||
|
div #{levels.length} #{course.get('name')}
|
||||||
|
div #{levelsTotal} levels total
|
||||||
|
each course in view.courses.models
|
||||||
|
- var campaign = view.campaigns.get(course.get('campaignID'));
|
||||||
|
- var levels = campaign.getLevels().models;
|
||||||
|
- levelCounts = levels.length;
|
||||||
|
strong #{course.get('name')}
|
||||||
|
.small= course.get('description')
|
||||||
|
.small Levels last updated #{campaign.get('levelsUpdated')}
|
||||||
|
table.table.table-striped.table-condensed
|
||||||
|
tr
|
||||||
|
th= levels.length
|
||||||
|
th Slug
|
||||||
|
th Type
|
||||||
|
th Practice
|
||||||
|
th Practice Threshold (m)
|
||||||
|
each level, levelIndex in levels
|
||||||
|
- var levelNumber = campaign.getLevelNumber(level.get('original'), levelIndex + 1)
|
||||||
|
tr
|
||||||
|
td= levelNumber
|
||||||
|
td= level.get('slug')
|
||||||
|
td= level.get('type')
|
||||||
|
td= level.get('practice')
|
||||||
|
td= level.get('practiceThresholdMinutes')
|
|
@ -14,8 +14,9 @@ block content
|
||||||
p
|
p
|
||||||
div Untriaged students: #{view.untriagedStudents}
|
div Untriaged students: #{view.untriagedStudents}
|
||||||
div Untriaged teachers: #{view.untriagedTeachers}
|
div Untriaged teachers: #{view.untriagedTeachers}
|
||||||
.small Teacher: owns a classroom or has a teacher role
|
.small Teacher: teacherish role or owns a classroom
|
||||||
.small Student: member of a classroom or has schoolName set, not in HoC course instance
|
.small Student: student role or member of a classroom or has schoolName set, not in HoC course instance
|
||||||
|
.small School: trial request data or teacher with 10+ students
|
||||||
.small +3 USA states are GU, PR, DC
|
.small +3 USA states are GU, PR, DC
|
||||||
|
|
||||||
p
|
p
|
||||||
|
|
|
@ -77,7 +77,7 @@ block content
|
||||||
div.row(class=(test.isSuccessful() && id > 1 ? 'collapse' : 'collapse in'), id='verifier-test-' + id)
|
div.row(class=(test.isSuccessful() && id > 1 ? 'collapse' : 'collapse in'), id='verifier-test-' + id)
|
||||||
div.col-xs-8
|
div.col-xs-8
|
||||||
if test.solution
|
if test.solution
|
||||||
pre #{test.solution.source}
|
pre.solution #{test.solution.source}
|
||||||
else
|
else
|
||||||
h4 Error Loading Test
|
h4 Error Loading Test
|
||||||
pre #{test.error}
|
pre #{test.error}
|
||||||
|
|
16
app/views/admin/AdminClassroomLevelsView.coffee
Normal file
16
app/views/admin/AdminClassroomLevelsView.coffee
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
RootView = require 'views/core/RootView'
|
||||||
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
|
Campaigns = require 'collections/Campaigns'
|
||||||
|
Course = require 'models/Course'
|
||||||
|
|
||||||
|
module.exports = class AdminClassroomLevelsView extends RootView
|
||||||
|
id: 'admin-classroom-levels-view'
|
||||||
|
template: require 'templates/admin/admin-classroom-levels'
|
||||||
|
|
||||||
|
initialize: ->
|
||||||
|
return super() unless me.isAdmin()
|
||||||
|
@campaigns = new Campaigns()
|
||||||
|
@supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } })
|
||||||
|
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||||
|
@supermodel.loadCollection(@courses, 'courses')
|
||||||
|
super()
|
|
@ -7,6 +7,10 @@ User = require 'models/User'
|
||||||
utils = require 'core/utils'
|
utils = require 'core/utils'
|
||||||
|
|
||||||
# TODO: match anonymous trial requests with real users via email
|
# TODO: match anonymous trial requests with real users via email
|
||||||
|
# TODO: sanitize and use student.schoolName, can't use it directly
|
||||||
|
# TODO: example untriaged student: no geo IP, not attached to teacher with school
|
||||||
|
# TODO: example untriaged teacher: deleted but owner of a classroom
|
||||||
|
# TODO: use student geoip on their teacher
|
||||||
|
|
||||||
module.exports = class SchoolCountsView extends RootView
|
module.exports = class SchoolCountsView extends RootView
|
||||||
id: 'admin-school-counts-view'
|
id: 'admin-school-counts-view'
|
||||||
|
@ -35,7 +39,7 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
studentMap = {} # Used to make sure teachers and students only counted once
|
studentMap = {} # Used to make sure teachers and students only counted once
|
||||||
studentNonHocMap = {} # Used to exclude HoC users
|
studentNonHocMap = {} # Used to exclude HoC users
|
||||||
teacherStudentMap = {} # Used to link students to their teacher locations
|
teacherStudentMap = {} # Used to link students to their teacher locations
|
||||||
countryStateDistrictSchoolCountsMap = {} # Data graph
|
unknownSchoolCount = 1 # Used to separate unique but unknown schools
|
||||||
|
|
||||||
console.log(new Date().toISOString(), "Processing #{@courseInstances.models.length} course instances...")
|
console.log(new Date().toISOString(), "Processing #{@courseInstances.models.length} course instances...")
|
||||||
for courseInstance in @courseInstances.models
|
for courseInstance in @courseInstances.models
|
||||||
|
@ -46,25 +50,29 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
for classroom in @classrooms.models
|
for classroom in @classrooms.models
|
||||||
teacherID = classroom.get('ownerID')
|
teacherID = classroom.get('ownerID')
|
||||||
teacherMap[teacherID] ?= {}
|
teacherMap[teacherID] ?= {}
|
||||||
teacherMap[teacherID] = true
|
|
||||||
teacherStudentMap[teacherID] ?= {}
|
teacherStudentMap[teacherID] ?= {}
|
||||||
for studentID in classroom.get('members')
|
for studentID in classroom.get('members')
|
||||||
|
continue if teacherMap[studentID]
|
||||||
continue unless studentNonHocMap[studentID]
|
continue unless studentNonHocMap[studentID]
|
||||||
studentMap[studentID] = true
|
studentMap[studentID] = {}
|
||||||
teacherStudentMap[teacherID][studentID] = true
|
teacherStudentMap[teacherID][studentID] = true
|
||||||
|
|
||||||
console.log(new Date().toISOString(), "Processing #{@teachers.models.length} teachers...")
|
console.log(new Date().toISOString(), "Processing #{@teachers.models.length} teachers...")
|
||||||
for teacher in @teachers.models
|
for teacher in @teachers.models
|
||||||
teacherMap[teacher.id] ?= {}
|
teacherMap[teacher.id] = teacher.get('geo') ? {}
|
||||||
delete studentMap[teacher.id]
|
delete studentMap[teacher.id]
|
||||||
|
|
||||||
console.log(new Date().toISOString(), "Processing #{@students.models.length} students...")
|
console.log(new Date().toISOString(), "Processing #{@students.models.length} students...")
|
||||||
for student in @students.models when not teacherMap[student.id]
|
for student in @students.models
|
||||||
continue unless studentNonHocMap[student.id]
|
continue unless studentNonHocMap[student.id]
|
||||||
schoolName = student.get('schoolName')
|
continue if teacherMap[student.id]
|
||||||
studentMap[student.id] = true
|
studentMap[student.id] = {geo: student.get('geo')}
|
||||||
|
|
||||||
console.log(new Date().toISOString(), "Processing trial #{@trialRequests.models.length} requests...")
|
orphanStudentMap = _.cloneDeep(studentMap)
|
||||||
|
orphanTeacherMap = _.cloneDeep(teacherMap)
|
||||||
|
|
||||||
|
console.log(new Date().toISOString(), "Processing #{@trialRequests.models.length} trial requests...")
|
||||||
|
countryStateDistrictSchoolCountsMap = {}
|
||||||
for trialRequest in @trialRequests.models
|
for trialRequest in @trialRequests.models
|
||||||
teacherID = trialRequest.get('applicant')
|
teacherID = trialRequest.get('applicant')
|
||||||
unless teacherMap[teacherID]
|
unless teacherMap[teacherID]
|
||||||
|
@ -82,8 +90,10 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
|
countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
|
countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
||||||
for studentID, val of teacherStudentMap[teacherID]
|
for studentID, val of teacherStudentMap[teacherID] when orphanStudentMap[studentID]
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||||
|
delete orphanStudentMap[studentID]
|
||||||
|
delete orphanTeacherMap[teacherID]
|
||||||
else if not _.isEmpty(props.country)
|
else if not _.isEmpty(props.country)
|
||||||
country = props.country?.trim()
|
country = props.country?.trim()
|
||||||
country = country[0].toUpperCase() + country.substring(1).toLowerCase()
|
country = country[0].toUpperCase() + country.substring(1).toLowerCase()
|
||||||
|
@ -102,8 +112,65 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
|
countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
|
countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
||||||
for studentID, val of teacherStudentMap[teacherID]
|
for studentID, val of teacherStudentMap[teacherID] when orphanStudentMap[studentID]
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||||
|
delete orphanStudentMap[studentID]
|
||||||
|
delete orphanTeacherMap[teacherID]
|
||||||
|
|
||||||
|
console.log(new Date().toISOString(), "Processing #{Object.keys(orphanTeacherMap).length} orphaned teachers with geo IPs...")
|
||||||
|
for teacherID, val of orphanTeacherMap
|
||||||
|
continue unless teacherMap[teacherID].country
|
||||||
|
country = teacherMap[teacherID].countryName or teacherMap[teacherID].country
|
||||||
|
country = 'UK' if country is 'GB' or country is 'United Kingdom'
|
||||||
|
country = 'USA' if country is 'US' or country is 'United States'
|
||||||
|
state = teacherMap[teacherID].region or 'unknown'
|
||||||
|
district = 'unknown'
|
||||||
|
school = 'unknown'
|
||||||
|
if teacherStudentMap[teacherID] and Object.keys(teacherStudentMap[teacherID]).length >= 10
|
||||||
|
school += unknownSchoolCount++
|
||||||
|
countryStateDistrictSchoolCountsMap[country] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
||||||
|
if teacherStudentMap[teacherID] and Object.keys(teacherStudentMap[teacherID]).length >= 10
|
||||||
|
for studentID, val of teacherStudentMap[teacherID] when orphanStudentMap[studentID]
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||||
|
delete orphanStudentMap[studentID]
|
||||||
|
delete orphanTeacherMap[teacherID]
|
||||||
|
|
||||||
|
console.log(new Date().toISOString(), "Processing #{Object.keys(orphanTeacherMap).length} orphaned teachers with 10+ students...")
|
||||||
|
for teacherID, val of orphanTeacherMap
|
||||||
|
continue unless teacherStudentMap[teacherID] and Object.keys(teacherStudentMap[teacherID]).length >= 10
|
||||||
|
country = 'unknown'
|
||||||
|
state = 'unknown'
|
||||||
|
district = 'unknown'
|
||||||
|
school = "unknown#{unknownSchoolCount++}"
|
||||||
|
countryStateDistrictSchoolCountsMap[country] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
||||||
|
for studentID, val of teacherStudentMap[teacherID] when orphanStudentMap[studentID]
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||||
|
delete orphanStudentMap[studentID]
|
||||||
|
delete orphanTeacherMap[teacherID]
|
||||||
|
|
||||||
|
console.log(new Date().toISOString(), "Processing #{Object.keys(orphanStudentMap).length} orphaned students with geo IPs...")
|
||||||
|
for studentID, val of orphanStudentMap
|
||||||
|
continue unless studentMap[studentID].geo?.country
|
||||||
|
country = studentMap[studentID].geo.countryName or studentMap[studentID].geo.country
|
||||||
|
country = 'UK' if country is 'GB' or country is 'United Kingdom'
|
||||||
|
country = 'USA' if country is 'US' or country is 'United States'
|
||||||
|
state = studentMap[studentID].geo.region or 'unknown'
|
||||||
|
district = 'unknown'
|
||||||
|
school = 'unknown'
|
||||||
|
countryStateDistrictSchoolCountsMap[country] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||||
|
delete orphanStudentMap[studentID]
|
||||||
|
|
||||||
console.log(new Date().toISOString(), 'Building country graphs...')
|
console.log(new Date().toISOString(), 'Building country graphs...')
|
||||||
@countryGraphs = {}
|
@countryGraphs = {}
|
||||||
|
@ -146,11 +213,16 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
schools: @countryGraphs[country].totalSchools
|
schools: @countryGraphs[country].totalSchools
|
||||||
students: @countryGraphs[country].totalStudents
|
students: @countryGraphs[country].totalStudents
|
||||||
teachers: @countryGraphs[country].totalTeachers
|
teachers: @countryGraphs[country].totalTeachers
|
||||||
totalStudents += @countryGraphs[country].totalSchools
|
totalStudents += @countryGraphs[country].totalStudents
|
||||||
totalTeachers += @countryGraphs[country].totalTeachers
|
totalTeachers += @countryGraphs[country].totalTeachers
|
||||||
|
|
||||||
|
# Compare against orphanStudentMap and orphanTeacherMap to catch bugs
|
||||||
@untriagedStudents = Object.keys(studentMap).length - totalStudents
|
@untriagedStudents = Object.keys(studentMap).length - totalStudents
|
||||||
@untriagedTeachers = Object.keys(teacherMap).length - totalTeachers
|
@untriagedTeachers = Object.keys(teacherMap).length - totalTeachers
|
||||||
|
|
||||||
|
console.log(new Date().toISOString(), "teacherMap #{Object.keys(teacherMap).length} totalTeachers #{totalTeachers} orphanTeacherMap #{Object.keys(orphanTeacherMap).length} @untriagedTeachers #{@untriagedTeachers}")
|
||||||
|
console.log(new Date().toISOString(), "studentMap #{Object.keys(studentMap).length} totalStudents #{totalStudents} orphanStudentMap #{Object.keys(orphanStudentMap).length} @untriagedStudents #{@untriagedStudents}")
|
||||||
|
|
||||||
for country, graph of @countryGraphs
|
for country, graph of @countryGraphs
|
||||||
graph.stateCounts.sort (a, b) ->
|
graph.stateCounts.sort (a, b) ->
|
||||||
b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.districts - a.districts or b.state.localeCompare(a.state)
|
b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.districts - a.districts or b.state.localeCompare(a.state)
|
||||||
|
|
|
@ -30,6 +30,7 @@ module.exports = class VerifierView extends RootView
|
||||||
if @levelID
|
if @levelID
|
||||||
@levelIDs = [@levelID]
|
@levelIDs = [@levelID]
|
||||||
@testLanguages = ['python', 'javascript', 'java', 'lua', 'coffeescript']
|
@testLanguages = ['python', 'javascript', 'java', 'lua', 'coffeescript']
|
||||||
|
@cores = 1
|
||||||
@startTestingLevels()
|
@startTestingLevels()
|
||||||
else
|
else
|
||||||
@campaigns = new Campaigns()
|
@campaigns = new Campaigns()
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
"co-express": "^1.2.1",
|
"co-express": "^1.2.1",
|
||||||
"coffee-script": "1.9.x",
|
"coffee-script": "1.9.x",
|
||||||
"connect": "2.7.x",
|
"connect": "2.7.x",
|
||||||
|
"country-list": "0.0.3",
|
||||||
"express": "~3.0.6",
|
"express": "~3.0.6",
|
||||||
"express-useragent": "~0.0.9",
|
"express-useragent": "~0.0.9",
|
||||||
"geoip-lite": "^1.1.6",
|
"geoip-lite": "^1.1.6",
|
||||||
|
@ -101,7 +102,6 @@
|
||||||
"commonjs-require-definition": "0.2.0",
|
"commonjs-require-definition": "0.2.0",
|
||||||
"compressible": "~1.0.1",
|
"compressible": "~1.0.1",
|
||||||
"country-data": "0.0.24",
|
"country-data": "0.0.24",
|
||||||
"country-list": "0.0.3",
|
|
||||||
"css-brunch": "^1.7.0",
|
"css-brunch": "^1.7.0",
|
||||||
"fs-extra": "^0.26.2",
|
"fs-extra": "^0.26.2",
|
||||||
"http-proxy": "^1.13.2",
|
"http-proxy": "^1.13.2",
|
||||||
|
|
|
@ -408,7 +408,7 @@ UserHandler = class UserHandler extends Handler
|
||||||
|
|
||||||
getSubSponsors: (req, res) ->
|
getSubSponsors: (req, res) ->
|
||||||
return @sendForbiddenError(res) unless req.user?.isAdmin()
|
return @sendForbiddenError(res) unless req.user?.isAdmin()
|
||||||
Payment.find {$where: 'this.purchaser.valueOf() != this.recipient.valueOf()'}, (err, payments) =>
|
Payment.find {$where: 'this.purchaser && this.recipient && this.purchaser.valueOf() != this.recipient.valueOf()'}, (err, payments) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
sponsorIDs = (payment.get('purchaser') for payment in payments)
|
sponsorIDs = (payment.get('purchaser') for payment in payments)
|
||||||
User.find {$and: [{_id: {$in: sponsorIDs}}, {"stripe.sponsorSubscriptionID": {$exists: true}}]}, (err, users) =>
|
User.find {$and: [{_id: {$in: sponsorIDs}}, {"stripe.sponsorSubscriptionID": {$exists: true}}]}, (err, users) =>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
_ = require 'lodash'
|
_ = require 'lodash'
|
||||||
co = require 'co'
|
co = require 'co'
|
||||||
|
countryList = require('country-list')()
|
||||||
errors = require '../commons/errors'
|
errors = require '../commons/errors'
|
||||||
|
geoip = require 'geoip-lite'
|
||||||
wrap = require 'co-express'
|
wrap = require 'co-express'
|
||||||
Promise = require 'bluebird'
|
Promise = require 'bluebird'
|
||||||
parse = require '../commons/parse'
|
parse = require '../commons/parse'
|
||||||
|
@ -10,7 +12,6 @@ sendwithus = require '../sendwithus'
|
||||||
User = require '../models/User'
|
User = require '../models/User'
|
||||||
Classroom = require '../models/Classroom'
|
Classroom = require '../models/Classroom'
|
||||||
|
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
fetchByGPlusID: wrap (req, res, next) ->
|
fetchByGPlusID: wrap (req, res, next) ->
|
||||||
gpID = req.query.gplusID
|
gpID = req.query.gplusID
|
||||||
|
@ -97,11 +98,22 @@ module.exports =
|
||||||
|
|
||||||
getStudents: wrap (req, res, next) ->
|
getStudents: wrap (req, res, next) ->
|
||||||
throw new errors.Unauthorized('You must be an administrator.') unless req.user?.isAdmin()
|
throw new errors.Unauthorized('You must be an administrator.') unless req.user?.isAdmin()
|
||||||
students = yield User.find({$and: [{schoolName: {$exists: true}}, {schoolName: {$ne: ''}}, {anonymous: false}]}).select('schoolName').lean()
|
query = $or: [{role: 'student'}, {$and: [{schoolName: {$exists: true}}, {schoolName: {$ne: ''}}, {anonymous: false}]}]
|
||||||
res.status(200).send(students)
|
users = yield User.find(query).select('lastIP schoolName').lean()
|
||||||
|
for user in users
|
||||||
|
if ip = user.lastIP
|
||||||
|
user.geo = geoip.lookup(ip)
|
||||||
|
if country = user.geo?.country
|
||||||
|
user.geo.countryName = countryList.getName(country)
|
||||||
|
res.status(200).send(users)
|
||||||
|
|
||||||
getTeachers: wrap (req, res, next) ->
|
getTeachers: wrap (req, res, next) ->
|
||||||
throw new errors.Unauthorized('You must be an administrator.') unless req.user?.isAdmin()
|
throw new errors.Unauthorized('You must be an administrator.') unless req.user?.isAdmin()
|
||||||
teacherRoles = ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']
|
teacherRoles = ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']
|
||||||
teachers = yield User.find(anonymous: false, role: {$in: teacherRoles}).select('').lean()
|
users = yield User.find(anonymous: false, role: {$in: teacherRoles}).select('lastIP').lean()
|
||||||
res.status(200).send(teachers)
|
for user in users
|
||||||
|
if ip = user.lastIP
|
||||||
|
user.geo = geoip.lookup(ip)
|
||||||
|
if country = user.geo?.country
|
||||||
|
user.geo.countryName = countryList.getName(country)
|
||||||
|
res.status(200).send(users)
|
||||||
|
|
Loading…
Reference in a new issue