mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Update lead status values for Close.io inbound inserts
This commit is contained in:
parent
3c715ceba8
commit
49c1dd8cf7
2 changed files with 187 additions and 136 deletions
|
@ -118,6 +118,7 @@
|
||||||
"marked": "0.2.x",
|
"marked": "0.2.x",
|
||||||
"nock": "^2.17.0",
|
"nock": "^2.17.0",
|
||||||
"nodemon": "1.6.1",
|
"nodemon": "1.6.1",
|
||||||
|
"parse-domain": "^0.2.1",
|
||||||
"requirejs": "~2.1.10",
|
"requirejs": "~2.1.10",
|
||||||
"sass-brunch": "https://github.com/basicer/sass-brunch-bleeding/archive/1.9.1-bleeding.tar.gz",
|
"sass-brunch": "https://github.com/basicer/sass-brunch-bleeding/archive/1.9.1-bleeding.tar.gz",
|
||||||
"telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master",
|
"telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master",
|
||||||
|
|
|
@ -27,8 +27,20 @@ const customFieldsToRemove = [
|
||||||
// Skip these problematic leads
|
// Skip these problematic leads
|
||||||
const leadsToSkip = ['6 sınıflar', 'fdsafd', 'ashtasht', 'matt+20160404teacher3 school', 'sdfdsf', 'ddddd', 'dsfadsaf', "Nolan's School of Wonders"];
|
const leadsToSkip = ['6 sınıflar', 'fdsafd', 'ashtasht', 'matt+20160404teacher3 school', 'sdfdsf', 'ddddd', 'dsfadsaf', "Nolan's School of Wonders"];
|
||||||
|
|
||||||
const demoRequestEmailTemplates = ['tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm', 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf'];
|
const demoRequestCloseIoEmailTemplates = ['tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm', 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf'];
|
||||||
const createTeacherEmailTemplates = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G'];
|
const createTeacherCloseIoEmailTemplates = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G'];
|
||||||
|
|
||||||
|
// Prioritized Close.io lead status match list
|
||||||
|
const closeIoInitialLeadStatuses = [
|
||||||
|
{status: 'Inbound UK Auto Attempt 1', regex: /^uk$|\.uk$/},
|
||||||
|
{status: 'Inbound Canada Auto Attempt 1', regex: /^ca$|\.ca$/},
|
||||||
|
{status: 'Inbound AU Auto Attempt 1', regex: /^au$|\.au$/},
|
||||||
|
{status: 'Inbound NZ Auto Attempt 1', regex: /^nz$|\.nz$/},
|
||||||
|
{status: 'New US Schools Auto Attempt 1', regex: /^us$|\.us$|\.gov$|k12|sd/},
|
||||||
|
{status: 'Inbound International Auto Attempt 1', regex: /^[A-Za-z]{2}$|\.[A-Za-z]{2}$/},
|
||||||
|
{status: 'Auto Attempt 1', regex: /^[A-Za-z]*$/}
|
||||||
|
];
|
||||||
|
|
||||||
const emailDelayMinutes = 27;
|
const emailDelayMinutes = 27;
|
||||||
|
|
||||||
const scriptStartTime = new Date();
|
const scriptStartTime = new Date();
|
||||||
|
@ -40,13 +52,16 @@ const intercomApiKey = intercomAppIdApiKey.split(':')[1];
|
||||||
const mongoConnUrl = process.argv[6];
|
const mongoConnUrl = process.argv[6];
|
||||||
const MongoClient = require('mongodb').MongoClient;
|
const MongoClient = require('mongodb').MongoClient;
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
const parseDomain = require('parse-domain');
|
||||||
const request = require('request');
|
const request = require('request');
|
||||||
|
|
||||||
const earliestDate = new Date();
|
const earliestDate = new Date();
|
||||||
earliestDate.setUTCDate(earliestDate.getUTCDate() - 10);
|
earliestDate.setUTCDate(earliestDate.getUTCDate() - 10);
|
||||||
|
|
||||||
|
// ** Main program
|
||||||
|
|
||||||
// log('DEBUG: Finding leads..');
|
// log('DEBUG: Finding leads..');
|
||||||
findLeads((err, leads) => {
|
findCocoLeads((err, leads) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
|
@ -69,10 +84,160 @@ findLeads((err, leads) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ** Utilities
|
||||||
|
|
||||||
/* Helpers */
|
function getInitialLeadStatusViaCountry(country) {
|
||||||
|
if (/usa|america|united states/ig.test(country)) {
|
||||||
|
return 'New US Schools Auto Attempt 1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Lead {
|
function getInitialLeadStatusViaEmails(emails) {
|
||||||
|
let currentStatus = null;
|
||||||
|
let currentRank = closeIoInitialLeadStatuses.length;
|
||||||
|
for (const email of emails) {
|
||||||
|
let tld = parseDomain(email).tld;
|
||||||
|
tld = tld ? tld.toLowerCase() : '';
|
||||||
|
for (let rank = 0; rank < currentRank; rank++) {
|
||||||
|
if (closeIoInitialLeadStatuses[rank].regex.test(tld)) {
|
||||||
|
currentStatus = closeIoInitialLeadStatuses[rank].status;
|
||||||
|
currentRank = rank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentStatus ? currentStatus : closeIoInitialLeadStatuses[closeIoInitialLeadStatuses.length - 1].status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomEmailApiKey() {
|
||||||
|
if (closeIoMailApiKeys.length < 0) return;
|
||||||
|
return closeIoMailApiKeys[Math.floor(Math.random() * closeIoMailApiKeys.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomEmailTemplate(templates) {
|
||||||
|
if (templates.length < 0) return '';
|
||||||
|
return templates[Math.floor(Math.random() * templates.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameEmailTemplateType(template1, template2) {
|
||||||
|
if (createTeacherCloseIoEmailTemplates.indexOf(template1) >= 0 && createTeacherCloseIoEmailTemplates.indexOf(template2) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (demoRequestCloseIoEmailTemplates.indexOf(template1) >= 0 && demoRequestCloseIoEmailTemplates.indexOf(template2) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(str) {
|
||||||
|
console.log(new Date().toISOString() + " " + str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ** Coco data collection methods and class
|
||||||
|
|
||||||
|
function findCocoLeads(done) {
|
||||||
|
MongoClient.connect(mongoConnUrl, (err, db) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
// Recent trial requests
|
||||||
|
const query = {$and: [{created: {$gte: earliestDate}}, {type: 'course'}]};
|
||||||
|
db.collection('trial.requests').find(query).toArray((err, trialRequests) => {
|
||||||
|
if (err) {
|
||||||
|
db.close();
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
const leads = {};
|
||||||
|
const emailLeadMap = {};
|
||||||
|
const emails = [];
|
||||||
|
for (const trialRequest of trialRequests) {
|
||||||
|
if (!trialRequest.properties || !trialRequest.properties.email) continue;
|
||||||
|
const email = trialRequest.properties.email.toLowerCase();
|
||||||
|
emails.push(email);
|
||||||
|
const name = trialRequest.properties.organization || trialRequest.properties.name || email;
|
||||||
|
if (!leads[name]) leads[name] = new CocoLead(name);
|
||||||
|
leads[name].addTrialRequest(email, trialRequest);
|
||||||
|
emailLeadMap[email] = leads[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users for trial requests
|
||||||
|
const query = {$and: [
|
||||||
|
{emailLower: {$in: emails}},
|
||||||
|
{anonymous: false}
|
||||||
|
]};
|
||||||
|
db.collection('users').find(query).toArray((err, users) => {
|
||||||
|
if (err) {
|
||||||
|
db.close();
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
const userIDs = [];
|
||||||
|
const userLeadMap = {};
|
||||||
|
const userEmailMap = {};
|
||||||
|
for (const user of users) {
|
||||||
|
const email = user.emailLower;
|
||||||
|
emailLeadMap[email].addUser(email, user);
|
||||||
|
userIDs.push(user._id);
|
||||||
|
userLeadMap[user._id.valueOf()] = emailLeadMap[email];
|
||||||
|
userEmailMap[user._id.valueOf()] = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classrooms for users
|
||||||
|
const query = {ownerID: {$in: userIDs}};
|
||||||
|
db.collection('classrooms').find(query).toArray((err, classrooms) => {
|
||||||
|
if (err) {
|
||||||
|
db.close();
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const classroom of classrooms) {
|
||||||
|
userLeadMap[classroom.ownerID.valueOf()].addClassroom(userEmailMap[classroom.ownerID.valueOf()], classroom);
|
||||||
|
}
|
||||||
|
db.close();
|
||||||
|
return done(null, leads);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAddIntercomDataFn(cocoLead, email) {
|
||||||
|
return (done) => {
|
||||||
|
const options = {
|
||||||
|
url: `https://api.intercom.io/users?email=${encodeURIComponent(email)}`,
|
||||||
|
auth: {
|
||||||
|
user: intercomAppId,
|
||||||
|
pass: intercomApiKey
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.get(options, (error, response, body) => {
|
||||||
|
if (error) return done(error);
|
||||||
|
try {
|
||||||
|
const user = JSON.parse(body);
|
||||||
|
cocoLead.addIntercomUser(email, user);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
console.log(body);
|
||||||
|
}
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addIntercomData(leads, done) {
|
||||||
|
const tasks = []
|
||||||
|
for (const name in leads) {
|
||||||
|
for (const email in leads[name].contacts) {
|
||||||
|
tasks.push(createAddIntercomDataFn(leads[name], email));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async.parallel(tasks, (err, results) => {
|
||||||
|
return done(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class CocoLead {
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.contacts = {};
|
this.contacts = {};
|
||||||
this.custom = {};
|
this.custom = {};
|
||||||
|
@ -105,11 +270,21 @@ class Lead {
|
||||||
addUser(email, user) {
|
addUser(email, user) {
|
||||||
this.contacts[email.toLowerCase()].user = user;
|
this.contacts[email.toLowerCase()].user = user;
|
||||||
}
|
}
|
||||||
|
getInitialLeadStatus() {
|
||||||
|
for (const email in this.contacts) {
|
||||||
|
const props = this.contacts[email].trial.properties;
|
||||||
|
if (props && props['country']) {
|
||||||
|
const status = getInitialLeadStatusViaCountry(props['country']);
|
||||||
|
if (status) return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getInitialLeadStatusViaEmails(Object.keys(this.contacts));
|
||||||
|
}
|
||||||
getLeadPostData() {
|
getLeadPostData() {
|
||||||
const postData = {
|
const postData = {
|
||||||
display_name: this.name,
|
display_name: this.name,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
status: 'Auto Attempted',
|
status: this.getInitialLeadStatus(),
|
||||||
contacts: this.getContactsPostData(),
|
contacts: this.getContactsPostData(),
|
||||||
custom: {
|
custom: {
|
||||||
lastUpdated: new Date(),
|
lastUpdated: new Date(),
|
||||||
|
@ -266,108 +441,7 @@ class Lead {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findLeads(done) {
|
// ** Upsert Close.io methods
|
||||||
MongoClient.connect(mongoConnUrl, (err, db) => {
|
|
||||||
if (err) return done(err);
|
|
||||||
|
|
||||||
// Recent trial requests
|
|
||||||
const query = {$and: [{created: {$gte: earliestDate}}, {type: 'course'}]};
|
|
||||||
db.collection('trial.requests').find(query).toArray((err, trialRequests) => {
|
|
||||||
if (err) {
|
|
||||||
db.close();
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
const leads = {};
|
|
||||||
const emailLeadMap = {};
|
|
||||||
const emails = [];
|
|
||||||
for (const trialRequest of trialRequests) {
|
|
||||||
if (!trialRequest.properties || !trialRequest.properties.email) continue;
|
|
||||||
const email = trialRequest.properties.email.toLowerCase();
|
|
||||||
emails.push(email);
|
|
||||||
const name = trialRequest.properties.organization || trialRequest.properties.name || email;
|
|
||||||
if (!leads[name]) leads[name] = new Lead(name);
|
|
||||||
leads[name].addTrialRequest(email, trialRequest);
|
|
||||||
emailLeadMap[email] = leads[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Users for trial requests
|
|
||||||
const query = {$and: [
|
|
||||||
{emailLower: {$in: emails}},
|
|
||||||
{anonymous: false}
|
|
||||||
]};
|
|
||||||
db.collection('users').find(query).toArray((err, users) => {
|
|
||||||
if (err) {
|
|
||||||
db.close();
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
const userIDs = [];
|
|
||||||
const userLeadMap = {};
|
|
||||||
const userEmailMap = {};
|
|
||||||
for (const user of users) {
|
|
||||||
const email = user.emailLower;
|
|
||||||
emailLeadMap[email].addUser(email, user);
|
|
||||||
userIDs.push(user._id);
|
|
||||||
userLeadMap[user._id.valueOf()] = emailLeadMap[email];
|
|
||||||
userEmailMap[user._id.valueOf()] = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Classrooms for users
|
|
||||||
const query = {ownerID: {$in: userIDs}};
|
|
||||||
db.collection('classrooms').find(query).toArray((err, classrooms) => {
|
|
||||||
if (err) {
|
|
||||||
db.close();
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const classroom of classrooms) {
|
|
||||||
userLeadMap[classroom.ownerID.valueOf()].addClassroom(userEmailMap[classroom.ownerID.valueOf()], classroom);
|
|
||||||
}
|
|
||||||
db.close();
|
|
||||||
return done(null, leads);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAddIntercomDataFn(lead, email) {
|
|
||||||
return (done) => {
|
|
||||||
const options = {
|
|
||||||
url: `https://api.intercom.io/users?email=${encodeURIComponent(email)}`,
|
|
||||||
auth: {
|
|
||||||
user: intercomAppId,
|
|
||||||
pass: intercomApiKey
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.get(options, (error, response, body) => {
|
|
||||||
if (error) return done(error);
|
|
||||||
try {
|
|
||||||
const user = JSON.parse(body);
|
|
||||||
lead.addIntercomUser(email, user);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
console.log(body);
|
|
||||||
}
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function addIntercomData(leads, done) {
|
|
||||||
const tasks = []
|
|
||||||
for (const name in leads) {
|
|
||||||
for (const email in leads[name].contacts) {
|
|
||||||
tasks.push(createAddIntercomDataFn(leads[name], email));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async.parallel(tasks, (err, results) => {
|
|
||||||
return done(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateExistingLead(lead, existingLead, done) {
|
function updateExistingLead(lead, existingLead, done) {
|
||||||
// console.log('DEBUG: updateExistingLead', existingLead.id);
|
// console.log('DEBUG: updateExistingLead', existingLead.id);
|
||||||
|
@ -445,10 +519,10 @@ function saveNewLead(lead, done) {
|
||||||
for (const contact of existingLead.contacts) {
|
for (const contact of existingLead.contacts) {
|
||||||
for (const email of contact.emails) {
|
for (const email of contact.emails) {
|
||||||
if (['create teacher', 'convert teacher'].indexOf(lead.contacts[email.email].trial.properties.siteOrigin) >= 0) {
|
if (['create teacher', 'convert teacher'].indexOf(lead.contacts[email.email].trial.properties.siteOrigin) >= 0) {
|
||||||
tasks.push(createSendEmailFn(email.email, existingLead.id, contact.id, getRandomEmailTemplate(createTeacherEmailTemplates)));
|
tasks.push(createSendEmailFn(email.email, existingLead.id, contact.id, getRandomEmailTemplate(createTeacherCloseIoEmailTemplates)));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tasks.push(createSendEmailFn(email.email, existingLead.id, contact.id, getRandomEmailTemplate(demoRequestEmailTemplates)));
|
tasks.push(createSendEmailFn(email.email, existingLead.id, contact.id, getRandomEmailTemplate(demoRequestCloseIoEmailTemplates)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -539,10 +613,10 @@ function createAddContactFn(postData, internalLead, externalLead) {
|
||||||
// Send emails to new contact
|
// Send emails to new contact
|
||||||
const email = postData.emails[0].email;
|
const email = postData.emails[0].email;
|
||||||
if (['create teacher', 'convert teacher'].indexOf(internalLead.contacts[email].trial.properties.siteOrigin) >= 0) {
|
if (['create teacher', 'convert teacher'].indexOf(internalLead.contacts[email].trial.properties.siteOrigin) >= 0) {
|
||||||
return sendMail(email, externalLead.id, newContact.id, getRandomEmailTemplate(createTeacherEmailTemplates), done);
|
return sendMail(email, externalLead.id, newContact.id, getRandomEmailTemplate(createTeacherCloseIoEmailTemplates), done);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return sendMail(email, externalLead.id, newContact.id, getRandomEmailTemplate(demoRequestEmailTemplates), done);
|
return sendMail(email, externalLead.id, newContact.id, getRandomEmailTemplate(demoRequestCloseIoEmailTemplates), done);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -572,26 +646,6 @@ function createAddNoteFn(leadId, newNote) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomEmailTemplate(templates) {
|
|
||||||
if (templates.length < 0) return '';
|
|
||||||
return templates[Math.floor(Math.random() * templates.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSameEmailTemplateType(template1, template2) {
|
|
||||||
if (createTeacherEmailTemplates.indexOf(template1) >= 0 && createTeacherEmailTemplates.indexOf(template2) >= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (demoRequestEmailTemplates.indexOf(template1) >= 0 && demoRequestEmailTemplates.indexOf(template2) >= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRandomEmailApiKey() {
|
|
||||||
if (closeIoMailApiKeys.length < 0) return;
|
|
||||||
return closeIoMailApiKeys[Math.floor(Math.random() * closeIoMailApiKeys.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSendEmailFn(email, leadId, contactId, template) {
|
function createSendEmailFn(email, leadId, contactId, template) {
|
||||||
return (done) => {
|
return (done) => {
|
||||||
return sendMail(email, leadId, contactId, template, done);
|
return sendMail(email, leadId, contactId, template, done);
|
||||||
|
@ -675,7 +729,3 @@ function updateLeads(leads, done) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(str) {
|
|
||||||
console.log(new Date().toISOString() + " " + str);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue