Merge pull request #8416 from BerkeleyTrue/feature/notify-on-completion

feature: notify camper by email on completion
This commit is contained in:
Quincy Larson
2016-05-04 15:10:00 -07:00
5 changed files with 159 additions and 35 deletions

View File

@ -0,0 +1,6 @@
import { Observable } from 'rx';
export default function extendEmail(app) {
const { Email } = app.models;
Email.send$ = Observable.fromNodeCallback(Email.send, Email);
}

View File

@ -1,4 +1,7 @@
import _ from 'lodash';
import moment from 'moment';
import loopback from 'loopback';
import path from 'path';
import dedent from 'dedent';
import { Observable } from 'rx';
import debug from 'debug';
@ -23,6 +26,20 @@ import {
import certTypes from '../utils/certTypes.json';
const log = debug('fcc:certification');
const renderCertifedEmail = loopback.template(path.join(
__dirname,
'..',
'views',
'emails',
'certified.ejs'
));
const renderNotifyEmail = loopback.template(path.join(
__dirname,
'..',
'views',
'emails',
'a-new-user.ejs'
));
const sendMessageToNonUser = ifNoUserSend(
'must be logged in to complete.'
);
@ -46,9 +63,85 @@ function getIdsForCert$(id, Challenge) {
.shareReplay();
}
// getFormatedDate(challengeMap: Object, challengeId: String) => String, throws
function getFormatedDate(challengeMap, challengeId) {
return moment(challengeMap[challengeId].completedDate)
.format('MMM Do, YYYY');
}
// sendCertifiedEmail(
// {
// email: String,
// username: String,
// isFrontEndCert: Boolean,
// isBackEndCert: Boolean,
// isDataVisCert: Boolean
// },
// send$: Observable
// ) => Observable
function sendCertifiedEmail(
{
email,
name,
username,
isFrontEndCert,
isBackEndCert,
isDataVisCert,
challengeMap
},
send$
) {
if (
!isFrontEndCert ||
!isBackEndCert ||
!isDataVisCert
) {
return Observable.just(false);
}
let frontEndDate;
let backEndDate;
let dataVisDate;
try {
frontEndDate = getFormatedDate(challengeMap, frontEndChallengeId);
backEndDate = getFormatedDate(challengeMap, backEndChallengeId);
dataVisDate = getFormatedDate(challengeMap, dataVisChallengeId);
} catch (err) {
return Observable.throw(err);
}
const notifyTeam = {
type: 'email',
to: 'Michael@FreeCodeCamp.com',
from: 'Team@FreeCodeCamp.com',
subject: 'A new user has arrived!',
text: renderNotifyEmail({
username,
name,
frontEndDate,
dataVisDate,
backEndDate
})
};
const notifyUser = {
type: 'email',
to: email,
from: 'Michael@FreeCodeCamp.com',
subject: 'Congratulation on gaining your third certification!',
text: renderCertifedEmail({
username,
name
})
};
return Observable.combineLatest(
send$(notifyTeam),
send$(notifyUser),
() => true
);
}
export default function certificate(app) {
const router = app.loopback.Router();
const { Challenge } = app.models;
const { Email, Challenge } = app.models;
const certTypeIds = {
[certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
@ -94,36 +187,46 @@ export default function certificate(app) {
challengeType
} = challenge;
if (
!user[certType] &&
isCertified(tests, user.challengeMap)
user[certType] ||
!isCertified(tests, user.challengeMap)
) {
const updateData = {
$set: {
[`challengeMap.${id}`]: {
id,
name,
completedDate: new Date(),
challengeType
},
[certType]: true
}
};
return req.user.update$(updateData)
// If user has commited to nonprofit,
// this will complete his pledge
.flatMap(
() => completeCommitment$(user),
({ count }, pledgeOrMessage) => {
if (typeof pledgeOrMessage === 'string') {
log(pledgeOrMessage);
}
log(`${count} documents updated`);
return true;
}
);
return Observable.just(false);
}
return Observable.just(false);
const updateData = {
$set: {
[`challengeMap.${id}`]: {
id,
name,
completedDate: new Date(),
challengeType
},
[certType]: true
}
};
// set here so sendCertifiedEmail works properly
// not used otherwise
user[certType] = true;
user.challengeMap[id] = { completedDate: new Date() };
return Observable.combineLatest(
// update user data
user.update$(updateData),
// If user has committed to nonprofit,
// this will complete their pledge
completeCommitment$(user),
// sends notification email is user has all three certs
// if not it noop
sendCertifiedEmail(user, Email.send$),
({ count }, pledgeOrMessage) => ({ count, pledgeOrMessage })
)
.map(
({ count, pledgeOrMessage }) => {
if (typeof pledgeOrMessage === 'string') {
log(pledgeOrMessage);
}
log(`${count} documents updated`);
return true;
}
);
})
.subscribe(
(didCertify) => {

View File

@ -474,12 +474,7 @@ module.exports = function(app) {
return Observable.just()
.doOnCompleted(() => {
req.flash('info', {
msg: dedent`
Once you have completed all of our challenges, you should
join our <a href="https://gitter.im/freecodecamp/HalfWayClub"
target="_blank">Half Way Club</a> and start getting
ready for our nonprofit projects.
`.split('\n').join(' ')
msg: 'You\'ve completed the last challenge!'
});
return res.redirect('/map');
});

View File

@ -0,0 +1,7 @@
Camper <%= username %> has completed all three certifications!
Completed front end cert on <%= frontEndDate %>.
Completed data vis cert on <%= dataVisDate %>.
Completed back end cert on <%= backEndDate %>.
https://www.freecodecamp.com/<%= username %>

View File

@ -0,0 +1,13 @@
Hi <%= name || username %>,
Congratulations on recently completing all three Free Code Camp certifications. But you have not yet finished Free Code Camp. The most important part still remains: the nonprofit projects.
These will help you contextualize and apply the raw skills you've picked up. You'll practice by building full stack JavaScript apps that real people will use. And in the end, you'll have code in production that you can showcase in your portfolio. This will be critical to your job search.
Please fill this out immediately: http://bit.ly/20VwLg0
Once you fill this out we will get back to you as quickly as possible to get you started.
Best,
Michael - nonprofit guy @ Free Code Camp