mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-31 07:12:49 -04:00
Merge branch 'master' into fix-1874
This commit is contained in:
commit
2db37c7556
12 changed files with 414 additions and 70 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -104,3 +104,4 @@ Dockerfile
|
|||
|
||||
# coffeelint for editors (might be standardized eventually)
|
||||
coffeelint.json
|
||||
gitSpy/*
|
||||
|
|
|
@ -8,12 +8,12 @@ module.exports.createAetherOptions = (options) ->
|
|||
throw new Error 'Specify a code language to create an Aether instance' unless options.codeLanguage
|
||||
|
||||
useInterpreter = options.useInterpreter
|
||||
defaultToEsper = switch options.codeLanguage
|
||||
when 'python' then me.level() < 15 # Esper currently works well until using range()
|
||||
when 'javascript' then me.level() < 22 # Esper currently works well until using hero.myFn = function() pattern
|
||||
when 'lua' then me.level() < 10 # Functions don't work in Esper yet, can't play forest function levels
|
||||
when 'coffeescript' then false # CoffeeScript has a toNative error if it ever finishes plan(), and also @fn = -> pattern doesn't work
|
||||
when 'clojure' then false # No Clojure support
|
||||
defaultToEsper = true #switch options.codeLanguage
|
||||
# when 'python' then me.level() < 15 # Esper currently works well until using range()
|
||||
# when 'javascript' then me.level() < 22 # Esper currently works well until using hero.myFn = function() pattern
|
||||
# when 'lua' then me.level() < 10 # Functions don't work in Esper yet, can't play forest function levels
|
||||
# when 'coffeescript' then false # CoffeeScript has a toNative error if it ever finishes plan(), and also @fn = -> pattern doesn't work
|
||||
# when 'clojure' then false # No Clojure support
|
||||
useInterpreter ?= !!utils.getQueryVariable 'esper', defaultToEsper
|
||||
aetherOptions =
|
||||
functionName: options.functionName
|
||||
|
|
|
@ -200,7 +200,7 @@
|
|||
do: "do"
|
||||
end: "end"
|
||||
function: "function"
|
||||
def: "def"
|
||||
def: "def" # (short for "define")
|
||||
self: "self"
|
||||
hero: "hero"
|
||||
this: "this"
|
||||
|
|
|
@ -188,22 +188,22 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
campaign_old_multiplayer_description: "Relíquias de uma era mais civilizada. Não há simulações em curso para estas arenas multijogador, mais antigas e sem heróis."
|
||||
|
||||
code:
|
||||
if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
|
||||
else: "else"
|
||||
elif: "elif"
|
||||
while: "while"
|
||||
loop: "loop"
|
||||
for: "for"
|
||||
break: "break"
|
||||
continue: "continue"
|
||||
then: "then"
|
||||
do: "do"
|
||||
end: "end"
|
||||
function: "function"
|
||||
if: "se" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
|
||||
else: "senão"
|
||||
elif: "senão se"
|
||||
while: "enquanto"
|
||||
loop: "circular"
|
||||
for: "para"
|
||||
break: "parar"
|
||||
continue: "continuar"
|
||||
then: "então"
|
||||
do: "fazer"
|
||||
end: "fim"
|
||||
function: "função"
|
||||
def: "def"
|
||||
self: "self"
|
||||
hero: "hero"
|
||||
this: "this"
|
||||
self: "próprio"
|
||||
hero: "herói"
|
||||
this: "isto"
|
||||
|
||||
share_progress_modal:
|
||||
blurb: "Estás a fazer grandes progressos! Conta ao teu educador o quanto aprendeste com o CodeCombat."
|
||||
|
|
|
@ -107,7 +107,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
blog: "Blog"
|
||||
forum: "Diễn đàn"
|
||||
account: "Tài khoản"
|
||||
# my_account: "My Account"
|
||||
my_account: "Quản lý tài khoản"
|
||||
profile: "Cá nhân"
|
||||
stats: "Chỉ số"
|
||||
code: "Code"
|
||||
|
@ -117,8 +117,8 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
about: "Chúng tôi"
|
||||
contact: "Liên hệ"
|
||||
twitter_follow: "Theo dõi"
|
||||
students: "Dành cho học viên"
|
||||
teachers: "Dành cho giáo viên"
|
||||
students: "Khu vực học viên"
|
||||
teachers: "Khu vực giáo viên"
|
||||
careers: "Cơ hội việc làm"
|
||||
facebook: "Facebook"
|
||||
twitter: "Twitter"
|
||||
|
@ -127,7 +127,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
learn_to_code: "Học Code!"
|
||||
# toggle_nav: "Toggle navigation"
|
||||
jobs: "Việc làm"
|
||||
schools: "Dành cho Trường Học"
|
||||
schools: "Trường Học"
|
||||
educator_wiki: "Wiki cho Giáo Viên"
|
||||
get_involved: "Tham Gia"
|
||||
open_source: "Mã nguồn mở (GitHub)"
|
||||
|
@ -155,7 +155,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
play_as: "Chơi là" # Ladder page
|
||||
compete: "Hoàn thành!" # Course details page
|
||||
spectate: "Quan sát" # Ladder page
|
||||
players: "Những người chơi" # Hover over a level on /play
|
||||
players: "người chơi" # Hover over a level on /play
|
||||
hours_played: "Thời gian chơi" # Hover over a level on /play
|
||||
items: "Trang bị" # Tooltip on item shop button from /play
|
||||
unlock: "Mua" # For purchasing items and heroes
|
||||
|
@ -705,8 +705,8 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
story_link: "Chúng tôi"
|
||||
press_link: "Liên hệ"
|
||||
mission_title: "Sứ mệnh của chúng tôi: đưa lập trình đến với mọi người trên Trái Đát này."
|
||||
mission_description_1: "<strong>Lập trình thật kì diệu</strong>. Bạn có thể sáng tạo ra một thứ gì đó từ trí tưởng tượng. Chúng tôi bắt đầu CodeCombat để cho học viên những trải nghiệm nhiệm màu khi <strong>viết code</strong>."
|
||||
mission_description_2: "Thực tế thì, điều này cũng giúp cho bạn học nhanh hơn. Nhanh hơn RẤT NHIỀU. Bạn được thực hành thay vì chỉ đọc lý thuyết. Chúng tôi muốn đưa môi trường thực hành này đến với trường học và đến tay <strong>mọi học viên</strong>, bởi vì mọi người đều cần có cơ hội biết đến sự nhiệm màu của lập trình."
|
||||
mission_description_1: "<strong>Lập trình thật kì diệu</strong>. Bạn có thể tạo ra một thứ gì đó chỉ từ trí tưởng tượng. Chúng tôi bắt đầu CodeCombat để đem tới cho học viên những trải nghiệm nhiệm màu khi <strong>viết code</strong> thực tế."
|
||||
mission_description_2: "Trên thực tế, việc này giúp cho bạn học nhanh hơn. Nhanh hơn RẤT NHIỀU. Bạn được thực hành thay vì chỉ đọc lý thuyết. Chúng tôi muốn đưa môi trường thực hành này đến với trường học và đến tay <strong>mọi học sinh</strong>, bởi vì mọi người đều cần có cơ hội biết đến sự nhiệm màu của lập trình."
|
||||
team_title: "Đội ngũ của CodeCombat"
|
||||
# team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our Github contributors, because we value growth and learning in our team."
|
||||
nick_title: "Đồng Sáng Lập, CEO" # {change}
|
||||
|
@ -720,11 +720,11 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
maka_blurb: "Người kể truyện"
|
||||
rob_title: "Kĩ Sư Phần Mềm"
|
||||
rob_blurb: "Code các thứ"
|
||||
josh_c_title: "Game Designer"
|
||||
josh_c_blurb: "Thiết kế game"
|
||||
josh_c_title: "Thiết Kế Game"
|
||||
josh_c_blurb: "Thiết kế trò chơi"
|
||||
robin_title: "Thiết Kế UX & Nghiên Cứu"
|
||||
# robin_blurb: "Scaffolding"
|
||||
josh_title: "Game Designer"
|
||||
josh_title: "Thiết Kế Game"
|
||||
josh_blurb: "Floor Is Lava"
|
||||
phoenix_title: "Kĩ Sư Phần Mềm"
|
||||
nolan_title: "Giám Đốc Khu Vực"
|
||||
|
@ -734,13 +734,13 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
jose_title: "Âm Nhạc"
|
||||
jose_blurb: "Taking Off"
|
||||
community_title: "...và cộng đồng mã nguồn mở của chúng tôi"
|
||||
# community_subtitle: "Over 450 contributors have helped build CodeCombat, with more joining every week!"
|
||||
# community_description_1: "CodeCombat is a community project, with hundreds of players volunteering to create levels, contribute to our code to add features, fix bugs, playtest, and even translate the game into 50 languages so far. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Anyone can join the CodeCombat community! Check out our"
|
||||
# community_description_link: "contribute page"
|
||||
# community_description_2: "for more info."
|
||||
# number_contributors: "Over 450 contributors have lent their support and time to this project."
|
||||
community_subtitle: "Trên 450 người đã tham gia đóng góp cho CodeCombat, và con số này tiếp tục tăng lên hàng tuần!"
|
||||
community_description_1: "CodeCombat là một dự án cộng đồng, với hàng trăm tình nguyện viên tham gia tạo các màn chơi, viết code để thêm tính năng mới, sửa lỗi, chơi thử, và thậm chí phiên dịch trò chơi sang 50 thứ tiếng khác nhau. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Bất cứ ai cũng có thể tham gia cộng đồng CodeCombat! Hãy ghé qua"
|
||||
community_description_link: "trang dành cho người đóng góp"
|
||||
community_description_2: "để biết thêm chi tiết."
|
||||
number_contributors: "Hơn 450 người đã bỏ công sức và thời gian để đóng góp cho dự án này."
|
||||
story_title: "Hãy nghe câu chuyện của chúng tôi"
|
||||
story_subtitle: "Từ năm 2013 cho đến nay, CodeCombat khởi nguồn chỉ là những phác thảo sơ khai đã được hiện thực hóa để trở thành một tựa game đầy lôi cuốn."
|
||||
story_subtitle: "Kể từ năm 2013 đến nay, CodeCombat khởi nguồn chỉ là những phác thảo sơ khai đã được hiện thực hóa để trở thành một tựa game đầy lôi cuốn."
|
||||
story_statistic_1a: "5,000,000+"
|
||||
story_statistic_1b: "người chơi"
|
||||
story_statistic_1c: "đã bắt đầu đặt chân vào thế giới lập trình cùng với CodeCombat"
|
||||
|
@ -748,13 +748,13 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
story_statistic_2b: "200+ quốc gia"
|
||||
story_statistic_3a: "Cùng với nhau, họ đã viết"
|
||||
story_statistic_3b: "hơn 1 tỷ dòng code"
|
||||
story_statistic_3c: "với rất nhiều ngôn ngữ lập trình khác nhau"
|
||||
# story_long_way_1: "Though we've come a long way..."
|
||||
# story_sketch_caption: "Nick's very first sketch depicting a programming game in action."
|
||||
# story_long_way_2: "we still have much to do before we complete our quest, so..."
|
||||
# jobs_title: "Come work with us and help write CodeCombat history!"
|
||||
# jobs_subtitle: "Don't see a good fit but interested in keeping in touch? See our \"Create Your Own\" listing."
|
||||
# jobs_benefits: "Employee Benefits"
|
||||
story_statistic_3c: "bằng rất nhiều ngôn ngữ lập trình khác nhau"
|
||||
story_long_way_1: "Mặc dù chúng tôi đã trải qua rất nhiều..."
|
||||
story_sketch_caption: "Bản phác thảo đầu tiên của Nick mô tả một tựa game lập trình."
|
||||
story_long_way_2: "chúng tôi vẫn còn rất nhiều điều phải làm để chinh phục sứ mệnh, vì thế..."
|
||||
jobs_title: "Hãy tham gia cùng chúng tôi và viết nên lịch sử CodeCombat!"
|
||||
jobs_subtitle: "Bạn muốn ứng tuyển nhưng không tìm thấy vị trí phù hợp? Hãy tham khảo mục \"Tự Ứng Tuyển\"."
|
||||
jobs_benefits: "Quyền Lợi Nhân Viên"
|
||||
# jobs_benefit_1: "Competitive salary and options"
|
||||
# jobs_benefit_2: "15 day minimum vacation policy, excluding company holidays"
|
||||
# jobs_benefit_3: "Flex time and flexible work-from-home"
|
||||
|
@ -766,23 +766,23 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# jobs_benefit_10: "Maternity leave: 10 weeks paid, next 6 @ 55% salary"
|
||||
# jobs_benefit_11: "Paternity leave: 10 weeks paid"
|
||||
learn_more: "Tìm hiểu thêm"
|
||||
# jobs_custom_title: "Create Your Own"
|
||||
# jobs_custom_description: "Are you passionate about CodeCombat but don't see a job listed that matches your qualifications? Write us and show how you think you can contribute to our team. We'd love to hear from you!"
|
||||
# jobs_custom_contact_1: "Send us a note at"
|
||||
# jobs_custom_contact_2: "introducing yourself and we might get in touch in the future!"
|
||||
# contact_title: "Press & Contact"
|
||||
# contact_subtitle: "Need more information? Get in touch with us at"
|
||||
# screenshots_title: "Game Screenshots"
|
||||
# screenshots_hint: "(click to view full size)"
|
||||
# downloads_title: "Download Assets & Information"
|
||||
about_codecombat: "Về CodeCombat"
|
||||
logo: "Logo"
|
||||
jobs_custom_title: "Tự Ứng Tuyển"
|
||||
jobs_custom_description: "Bạn hào hứng với CodeCombat nhưng không tìm thấy vị trí phù hợp? Hãy viết thư cho chúng tối và trình bày khả năng đóng góp của bạn cho đội ngũ của chúng tôi. Chúng tôi rất sẵn lòng nhận thông tin từ bạn!"
|
||||
jobs_custom_contact_1: "Nhắn lại cho chúng tôi tại"
|
||||
jobs_custom_contact_2: "và giới thiệu về bản thân bạn để chúng tôi có thể liên hệ lại trong tương lai!"
|
||||
contact_title: "Báo Chí & Liên Hệ"
|
||||
contact_subtitle: "Bạn cần thêm thông tin? Hãy liên lạc với chúng tôi tại"
|
||||
screenshots_title: "Ảnh Chụp Màn Hình Game"
|
||||
screenshots_hint: "(click để xem cỡ lớn)"
|
||||
downloads_title: "Download Tài Liệu & Thông Tin"
|
||||
about_codecombat: "Giới thiệu CodeCombat"
|
||||
logo: "Ảnh logo"
|
||||
screenshots: "Ảnh chụp màn hình"
|
||||
# character_art: "Character Art"
|
||||
# download_all: "Download All"
|
||||
character_art: "Ảnh nhân vật"
|
||||
download_all: "Tải Xuống Tất Cả"
|
||||
previous: "Trước"
|
||||
next: "Tiếp"
|
||||
# location_title: "We're located in downtown SF:"
|
||||
location_title: "Chúng tôi đóng quân tại trung tâm thành phố SF:"
|
||||
|
||||
teachers:
|
||||
who_for_title: "CodeCombat dành cho ai?"
|
||||
|
@ -855,7 +855,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
|
||||
contact:
|
||||
contact_us: "Liên hệ CodeCombat"
|
||||
welcome: "Rất vui vì được gặp bạn! Hãy điềm vào form dưới đây để gửi mail cho chúng tôi. "
|
||||
welcome: "Chào bạn! Hãy điềm vào form dưới đây để gửi mail cho chúng tôi."
|
||||
forum_prefix: "Để tìm hiểu thêm thông tin, hãy thử ghé qua "
|
||||
forum_page: "diễn đàn của chúng tôi"
|
||||
forum_suffix: "."
|
||||
|
|
|
@ -55,9 +55,9 @@ _.extend LevelSessionSchema.properties,
|
|||
title: 'Changed'
|
||||
readOnly: true
|
||||
|
||||
dateFirstCompleted: c.stringDate
|
||||
title: 'Completed'
|
||||
readOnly: true
|
||||
dateFirstCompleted: {} # c.stringDate
|
||||
# title: 'Completed'
|
||||
# readOnly: true
|
||||
|
||||
team: c.shortString()
|
||||
level: LevelSessionLevelSchema
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
utils = require 'core/utils'
|
||||
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/editor/verifier/verifier-view'
|
||||
VerifierTest = require './VerifierTest'
|
||||
|
@ -52,14 +54,14 @@ module.exports = class VerifierView extends RootView
|
|||
#testLevels = testLevels.slice 0, 15
|
||||
@linksQueryString = window.location.search
|
||||
@levelIDs = if @levelID then [@levelID] else testLevels
|
||||
|
||||
languages = utils.getQueryVariable 'languages', 'python,javascript'
|
||||
#supermodel = if @levelID then @supermodel else undefined
|
||||
@tests = []
|
||||
@taskList = []
|
||||
@tasksList = _.flatten _.map @levelIDs, (v) ->
|
||||
# TODO: offer good interface for choosing which languages, better performance for skipping missing solutions
|
||||
#_.map ['python', 'javascript', 'coffeescript', 'lua'], (l) ->
|
||||
_.map ['python', 'javascript'], (l) ->
|
||||
_.map languages.split(','), (l) ->
|
||||
#_.map ['javascript'], (l) ->
|
||||
level: v, language: l
|
||||
|
||||
|
|
97
scripts/analytics/mongodb/queries/maybeStudentTeacher.js
Normal file
97
scripts/analytics/mongodb/queries/maybeStudentTeacher.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
// Find users that may have incorrect roles based on classroom ownership and membership
|
||||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||
|
||||
// User buckets
|
||||
// Classroom owner, no role
|
||||
// Classroom owner, student role
|
||||
// Classroom owner, teacher role (GOOD)
|
||||
// Classroom member, no role
|
||||
// Classroom member, teacher role
|
||||
// Classroom member, student role (GOOD)
|
||||
|
||||
'use strict';
|
||||
const scriptStartTime = new Date();
|
||||
|
||||
const classrooms = db.classrooms.find({}, {ownerID: 1, members: 1}).toArray();
|
||||
print(classrooms.length, "classrooms");
|
||||
|
||||
const userIds = [];
|
||||
const teacherIds = {};
|
||||
const studentIds = {};
|
||||
for (var classroom of classrooms) {
|
||||
teacherIds[classroom.ownerID.valueOf()] = true;
|
||||
userIds.push(classroom.ownerID);
|
||||
for (var memberId of classroom.members) {
|
||||
studentIds[memberId.valueOf()] = true;
|
||||
userIds.push(memberId);
|
||||
}
|
||||
}
|
||||
|
||||
print(Object.keys(teacherIds).length, "users own classrooms");
|
||||
print(Object.keys(studentIds).length, "users in classrooms");
|
||||
print(Object.keys(teacherIds).length + Object.keys(studentIds).length, "total");
|
||||
|
||||
const users = db.users.find({$and: [{_id: {$in: userIds}}, {anonymous: false}]}, {email:1, role: 1}).toArray();
|
||||
|
||||
const studentOwnsClassroom = [];
|
||||
const teacherInClassroom = [];
|
||||
const individualInClassroom = [];
|
||||
const individualOwnsClassroom = [];
|
||||
for (var user of users) {
|
||||
const userId = user._id.valueOf();
|
||||
if (!user.email) {
|
||||
printjson(user);
|
||||
continue;
|
||||
}
|
||||
if (hasStudentRole(user.role)) {
|
||||
if (teacherIds[userId]) {
|
||||
studentOwnsClassroom.push(user.email);
|
||||
}
|
||||
}
|
||||
else if (hasTeacherRole(user.role)) {
|
||||
if (studentIds[userId]) {
|
||||
teacherInClassroom.push(user.email);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (studentIds[userId]) {
|
||||
individualInClassroom.push(user.email);
|
||||
}
|
||||
else if (teacherIds[userId]) {
|
||||
individualOwnsClassroom.push(user.email);
|
||||
}
|
||||
else {
|
||||
print("ERROR?", userId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print(studentOwnsClassroom.length, "Students own classroom:");
|
||||
for (var email of studentOwnsClassroom) {
|
||||
print(email);
|
||||
}
|
||||
print(teacherInClassroom.length, "Teachers in classroom:");
|
||||
for (var email of teacherInClassroom) {
|
||||
print(email);
|
||||
}
|
||||
print(individualInClassroom.length, "Individuals in classroom:");
|
||||
for (var email of individualInClassroom) {
|
||||
print(email);
|
||||
}
|
||||
print(individualOwnsClassroom.length, "Individuals own classroom:");
|
||||
for (var email of individualOwnsClassroom) {
|
||||
print(email);
|
||||
}
|
||||
|
||||
print("Script runtime: " + (new Date() - scriptStartTime));
|
||||
|
||||
function hasTeacherRole(role) {
|
||||
return ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent'].indexOf(role) >= 0;
|
||||
}
|
||||
|
||||
function hasStudentRole(role) {
|
||||
return ['student'].indexOf(role) >= 0;
|
||||
}
|
|
@ -11,6 +11,7 @@ if (process.argv.length !== 6) {
|
|||
// TODO: 2nd follow up email activity does not handle paged activity results
|
||||
// TODO: sendMail copied from updateCloseIoLeads.js
|
||||
// TODO: template values copied from updateCloseIoLeads.js
|
||||
// TODO: status change is not related to specific lead contacts
|
||||
|
||||
const createTeacherEmailTemplatesAuto1 = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G'];
|
||||
const demoRequestEmailTemplatesAuto1 = ['tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm', 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf'];
|
||||
|
@ -31,7 +32,9 @@ earliestDate.setUTCDate(earliestDate.getUTCDate() - 10);
|
|||
// ** Main program
|
||||
|
||||
async.series([
|
||||
sendSecondFollowupMails
|
||||
sendSecondFollowupMails,
|
||||
addCallTasks
|
||||
// TODO: Cancel call tasks
|
||||
],
|
||||
(err, results) => {
|
||||
if (err) console.error(err);
|
||||
|
@ -69,6 +72,14 @@ function isCreateTeacherTemplateAuto1(template) {
|
|||
return createTeacherEmailTemplatesAuto1.indexOf(template) >= 0;
|
||||
}
|
||||
|
||||
function isDemoRequestTemplateAuto2(template) {
|
||||
return demoRequestEmailTemplatesAuto2.indexOf(template) >= 0;
|
||||
}
|
||||
|
||||
function isCreateTeacherTemplateAuto2(template) {
|
||||
return createTeacherEmailTemplatesAuto2.indexOf(template) >= 0;
|
||||
}
|
||||
|
||||
function log(str) {
|
||||
console.log(new Date().toISOString() + " " + str);
|
||||
}
|
||||
|
@ -325,7 +336,9 @@ function sendSecondFollowupMails(done) {
|
|||
if (error) return done(error);
|
||||
try {
|
||||
const results = JSON.parse(body);
|
||||
console.log(`sendSecondFollowupMails total num leads ${results.total_results} has_more=${results.has_more}`);
|
||||
if (skip === 0) {
|
||||
console.log(`sendSecondFollowupMails total num leads ${results.total_results} has_more=${results.has_more}`);
|
||||
}
|
||||
has_more = results.has_more;
|
||||
const tasks = [];
|
||||
for (const lead of results.data) {
|
||||
|
@ -357,3 +370,227 @@ function sendSecondFollowupMails(done) {
|
|||
nextPage(0);
|
||||
});
|
||||
}
|
||||
|
||||
function createAddCallTaskFn(userApiKeyMap, latestDate, lead, email) {
|
||||
// Check for activity since second auto mail and status update
|
||||
// Add call task
|
||||
// TODO: Very similar function to createSendFollowupMailFn
|
||||
const auto1Statuses = ["Auto Attempt 1", "New US Schools Auto Attempt 1"];
|
||||
const auto2Statuses = ["Auto Attempt 2", "New US Schools Auto Attempt 2"];
|
||||
return (done) => {
|
||||
// console.log("DEBUG: addCallTask", lead.id);
|
||||
|
||||
// Skip leads with tasks
|
||||
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/task/?lead_id=${lead.id}`;
|
||||
request.get(url, (error, response, body) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return done();
|
||||
}
|
||||
try {
|
||||
const results = JSON.parse(body);
|
||||
if (results.total_results > 0) {
|
||||
// console.log(`DEBUG: ${lead.id} has ${results.total_results} tasks`);
|
||||
return done();
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
// Find all lead activities
|
||||
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/activity/?lead_id=${lead.id}`;
|
||||
request.get(url, (error, response, body) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return done();
|
||||
}
|
||||
try {
|
||||
const results = JSON.parse(body);
|
||||
if (results.has_more) {
|
||||
console.log(`ERROR: ${lead.id} has more activities than returned!`);
|
||||
return done();
|
||||
}
|
||||
|
||||
// Find second auto mail and status change
|
||||
let sentSecondCreateTeacherEmail = false;
|
||||
let sentSecondDemoRequestEmail = false;
|
||||
let secondMailActivity;
|
||||
let statusUpdateActivity;
|
||||
let contactReplyMail;
|
||||
for (const activity of results.data) {
|
||||
if (activity._type === 'Email' && activity.to[0] === email) {
|
||||
if (isCreateTeacherTemplateAuto2(activity.template_id)) {
|
||||
if (sentSecondCreateTeacherEmail || sentSecondDemoRequestEmail) {
|
||||
console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`);
|
||||
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;
|
||||
}
|
||||
}
|
||||
else if (activity._type === 'LeadStatusChange' && auto1Statuses.indexOf(activity.old_status_label) >= 0
|
||||
&& auto2Statuses.indexOf(activity.new_status_label) >= 0) {
|
||||
statusUpdateActivity = activity;
|
||||
}
|
||||
}
|
||||
|
||||
if (!secondMailActivity) {
|
||||
// console.log(`DEBUG: No auto2 mail sent for ${lead.id} ${email}`);
|
||||
return done();
|
||||
}
|
||||
if (!statusUpdateActivity) {
|
||||
console.log(`ERROR: No status update for ${lead.id} ${email}`);
|
||||
return done();
|
||||
}
|
||||
if (new Date(secondMailActivity.date_created) > latestDate) {
|
||||
// console.log(`DEBUG: Second auto mail too recent ${secondMailActivity.date_created} ${lead.id}`);
|
||||
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
|
||||
// Skip email to a different contact's email
|
||||
// Skip note about different contact
|
||||
let recentActivity;
|
||||
for (const activity of results.data) {
|
||||
if (activity.id === secondMailActivity.id) continue;
|
||||
if (activity.id === statusUpdateActivity.id) continue;
|
||||
if (new Date(secondMailActivity.date_created) > new Date(activity.date_created)) continue;
|
||||
if (new Date(statusUpdateActivity.date_created) > new Date(activity.date_created)) continue;
|
||||
if (activity._type === 'Note' && activity.note
|
||||
&& activity.note.indexOf('demo_email') >= 0 && activity.note.indexOf(email) < 0) {
|
||||
// console.log(`DEBUG: Skipping ${lead.id} ${email} auto import note for different contact`);
|
||||
// console.log(activity.note);
|
||||
continue;
|
||||
}
|
||||
recentActivity = activity;
|
||||
break;
|
||||
}
|
||||
|
||||
// Create call task
|
||||
if (!recentActivity) {
|
||||
console.log(`DEBUG: adding call task for ${lead.id} ${email}`);
|
||||
const postData = {
|
||||
_type: "lead",
|
||||
lead_id: lead.id,
|
||||
assigned_to: secondMailActivity.user_id,
|
||||
text: `Call ${email}`,
|
||||
is_complete: false
|
||||
};
|
||||
const options = {
|
||||
uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/task/`,
|
||||
body: JSON.stringify(postData)
|
||||
};
|
||||
request.post(options, (error, response, body) => {
|
||||
if (error) return done(error);
|
||||
const result = JSON.parse(body);
|
||||
if (result.errors || result['field-errors']) {
|
||||
const errorMessage = `Create call task POST error for ${email} ${lead.id}`;
|
||||
console.error(errorMessage);
|
||||
// console.error(body);
|
||||
// console.error(postData);
|
||||
return done(errorMessage);
|
||||
}
|
||||
return done();
|
||||
});
|
||||
}
|
||||
else {
|
||||
// console.log(`DEBUG: Found recent activity after auto2 mail for ${lead.id} ${email}`);
|
||||
// console.log(recentActivity);
|
||||
return done();
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
console.log(body);
|
||||
return done();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function addCallTasks(done) {
|
||||
// Find all leads with auto 2 status, created since earliestDate
|
||||
// TODO: Very similar function to sendSecondFollowupMails
|
||||
// console.log("DEBUG: addCallTasks");
|
||||
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] = apiKey;
|
||||
return done();
|
||||
});
|
||||
};
|
||||
}
|
||||
const tasks = [];
|
||||
for (const apiKey of closeIoMailApiKeys) {
|
||||
tasks.push(createGetUserFn(apiKey));
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
if (err) console.log(err);
|
||||
const latestDate = new Date();
|
||||
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 limit = 100;
|
||||
const nextPage = (skip) => {
|
||||
let has_more = false;
|
||||
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?_skip=${skip}&_limit=${limit}&query=${encodeURIComponent(query)}/`;
|
||||
request.get(url, (error, response, body) => {
|
||||
if (error) return done(error);
|
||||
try {
|
||||
const results = JSON.parse(body);
|
||||
if (skip === 0) {
|
||||
console.log(`addCallTasks total num leads ${results.total_results} has_more=${results.has_more}`);
|
||||
}
|
||||
has_more = results.has_more;
|
||||
const tasks = [];
|
||||
for (const lead of results.data) {
|
||||
// console.log(`${lead.id}\t${lead.status_label}\t${lead.name}`);
|
||||
// if (lead.id !== 'lead_foo') continue;
|
||||
const existingContacts = lead.contacts || [];
|
||||
for (const contact of existingContacts) {
|
||||
if (contact.emails && contact.emails.length > 0) {
|
||||
if (contact.phones && contact.phones.length > 0) {
|
||||
tasks.push(createAddCallTaskFn(userApiKeyMap, latestDate, lead, contact.emails[0].email.toLowerCase()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`ERROR: lead ${lead.id} contact has non-1 emails`);
|
||||
}
|
||||
}
|
||||
// if (tasks.length > 10) break;
|
||||
}
|
||||
async.series(tasks, (err, results) => {
|
||||
if (err) return done(err);
|
||||
if (has_more) {
|
||||
return nextPage(skip + limit);
|
||||
}
|
||||
return done(err);
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
nextPage(0);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class LevelSessionHandler extends Handler
|
|||
not document.submittedCode # TODO: only allow leaderboard access to non-top-5 solutions
|
||||
document = _.omit document, @privateProperties
|
||||
if req.query.interpret
|
||||
plan = submittedCode[if document.team is 'humans' then 'hero-placeholder' else 'hero-placeholder-1'].plan
|
||||
plan = submittedCode[if document.team is 'humans' then 'hero-placeholder' else 'hero-placeholder-1']?.plan ? ''
|
||||
plan = LZString.compressToUTF16 plan
|
||||
document.interpret = plan
|
||||
document.code = submittedCode
|
||||
|
|
|
@ -18,7 +18,14 @@ PatchSchema.pre 'save', (next) ->
|
|||
return next(err)
|
||||
|
||||
collection = target.collection
|
||||
handler = require('../' + handlers[collection])
|
||||
try
|
||||
handler = require('../' + handlers[collection])
|
||||
catch err
|
||||
console.error 'Couldn\'t find handler for collection:', target.collection, 'from target', target
|
||||
err = new Error('Server error.')
|
||||
err.response = {message: '', property: 'target.id'}
|
||||
err.code = 500
|
||||
return next(err)
|
||||
handler.getDocumentForIdOrSlug targetID, (err, document) =>
|
||||
if err
|
||||
err = new Error('Server error.')
|
||||
|
|
|
@ -30,7 +30,7 @@ module.exports.sendResponseObject = (res, object) ->
|
|||
module.exports.formatSessionInformation = (session) ->
|
||||
heroID = if session.team is 'ogres' then 'hero-placeholder-1' else 'hero-placeholder'
|
||||
submittedCode = {}
|
||||
submittedCode[heroID] = plan: LZString.compressToUTF16 session.submittedCode[heroID].plan
|
||||
submittedCode[heroID] = plan: LZString.compressToUTF16(session.submittedCode[heroID]?.plan ? '')
|
||||
|
||||
_id: session._id
|
||||
sessionID: session._id
|
||||
|
|
Loading…
Add table
Reference in a new issue