Skip to content

Commit 2c36de0

Browse files
authored
Merge pull request #437 from tejad/feature/notifications-scheduler
Implemented email notifications
2 parents 07082f4 + 9c2d368 commit 2c36de0

14 files changed

+1399
-102
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,6 @@ api.env
124124

125125
# macOS files
126126
.DS_Store
127+
128+
# rendered html for email template
129+
scripts/notification-renderer/rendered.html

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ To be able to change and test `taas-es-processor` locally you can follow the nex
221221
| `npm run delete-index` | Delete Elasticsearch indexes. Use `-- --force` flag to skip confirmation |
222222
| `npm run data:import <filePath>` | Imports data into ES and db from filePath (`./data/demo-data.json` is used as default). Use `-- --force` flag to skip confirmation |
223223
| `npm run data:export <filePath>` | Exports data from ES and db into filePath (`./data/demo-data.json` is used as default). Use `-- --force` flag to skip confirmation |
224+
| `npm run renderTemplate <notificationId>` | Generates `scripts/notification-renderer/rendered.html` which has the rendered email template for the given `notificationId` where `notificationId` is one of the keys in `data/notifications.json` ex: `npm run renderTemplate upcomingResourceBookingExpiration` |
224225
| `npm run index:all` | Indexes all data from db into ES. Use `-- --force` flag to skip confirmation |
225226
| `npm run index:jobs <jobId>` | Indexes job data from db into ES, if jobId is not given all data is indexed. Use `-- --force` flag to skip confirmation |
226227
| `npm run index:job-candidates <jobCandidateId>` | Indexes job candidate data from db into ES, if jobCandidateId is not given all data is indexed. Use `-- --force` flag to skip confirmation |

app-constants.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ const PaymentSchedulerStatus = {
152152
CLOSE_CHALLENGE: 'close-challenge'
153153
}
154154

155+
const JobStatus = {
156+
OPEN: 'open'
157+
}
158+
159+
const JobCandidateStatus = {
160+
INTERVIEW: 'interview'
161+
}
162+
155163
module.exports = {
156164
UserRoles,
157165
FullManagePermissionRoles,
@@ -164,5 +172,7 @@ module.exports = {
164172
PaymentSchedulerStatus,
165173
PaymentProcessingSwitch,
166174
PaymentStatusRules,
167-
ActiveWorkPeriodPaymentStatuses
175+
ActiveWorkPeriodPaymentStatuses,
176+
JobStatus,
177+
JobCandidateStatus
168178
}

app.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const logger = require('./src/common/logger')
1414
const eventHandlers = require('./src/eventHandlers')
1515
const interviewService = require('./src/services/InterviewService')
1616
const { processScheduler } = require('./src/services/PaymentSchedulerService')
17+
const emailNotificationService = require('./src/services/EmailNotificationService')
1718

1819
// setup express app
1920
const app = express()
@@ -101,6 +102,12 @@ const server = app.listen(app.get('port'), () => {
101102

102103
// schedule payment processing
103104
schedule.scheduleJob(config.PAYMENT_PROCESSING.CRON, processScheduler)
105+
106+
schedule.scheduleJob(config.CRON_CANDIDATE_REVIEW, emailNotificationService.sendCandidatesAvailableEmails)
107+
schedule.scheduleJob(config.CRON_INTERVIEW_COMING_UP, emailNotificationService.sendInterviewComingUpEmails)
108+
schedule.scheduleJob(config.CRON_INTERVIEW_COMPLETED, emailNotificationService.sendInterviewCompletedEmails)
109+
schedule.scheduleJob(config.CRON_POST_INTERVIEW, emailNotificationService.sendPostInterviewActionEmails)
110+
schedule.scheduleJob(config.CRON_UPCOMING_RESOURCE_BOOKING, emailNotificationService.sendResourceBookingExpirationEmails)
104111
})
105112

106113
if (process.env.NODE_ENV === 'test') {

config/default.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ module.exports = {
147147

148148
// the Kafka message topic for sending email
149149
EMAIL_TOPIC: process.env.EMAIL_TOPIC || 'external.action.email',
150+
// the Kafka message topic for creating notifications
151+
NOTIFICATIONS_CREATE_TOPIC: process.env.NOTIFICATIONS_CREATE_TOPIC || 'notifications.action.create',
150152
// the emails address for receiving the issue report
151153
// REPORT_ISSUE_EMAILS may contain comma-separated list of email which is converted to array
152154
REPORT_ISSUE_EMAILS: (process.env.REPORT_ISSUE_EMAILS || '').split(','),
@@ -226,5 +228,25 @@ module.exports = {
226228
interview: 'withdrawn',
227229
selected: 'withdrawn',
228230
offered: 'withdrawn'
229-
}
231+
},
232+
// the sender email
233+
NOTIFICATION_SENDER_EMAIL: process.env.NOTIFICATION_SENDER_EMAIL,
234+
// the email notification sendgrid template id
235+
NOTIFICATION_SENDGRID_TEMPLATE_ID: process.env.NOTIFICATION_SENDGRID_TEMPLATE_ID,
236+
// hours after interview completed when we should post the notification
237+
INTERVIEW_COMPLETED_NOTIFICATION_HOURS: process.env.INTERVIEW_COMPLETED_NOTIFICATION_HOURS || 4,
238+
// no of weeks before expiry when we should post the notification
239+
RESOURCE_BOOKING_EXPIRY_NOTIFICATION_WEEKS: process.env.RESOURCE_BOOKING_EXPIRY_NOTIFICATION_WEEKS || 3,
240+
// frequency of cron checking for available candidates for review
241+
CRON_CANDIDATE_REVIEW: process.env.CRON_CANDIDATE_REVIEW || '00 00 13 * * 0-6',
242+
// frequency of cron checking for coming up interviews
243+
// when changing this to frequency other than 5 mins, please change the minutesRange in sendInterviewComingUpEmails correspondingly
244+
CRON_INTERVIEW_COMING_UP: process.env.CRON_INTERVIEW_COMING_UP || '*/5 * * * *',
245+
// frequency of cron checking for interview completed
246+
// when changing this to frequency other than 5 mins, please change the minutesRange in sendInterviewCompletedEmails correspondingly
247+
CRON_INTERVIEW_COMPLETED: process.env.CRON_INTERVIEW_COMPLETED || '*/5 * * * *',
248+
// frequency of cron checking for post interview actions
249+
CRON_POST_INTERVIEW: process.env.CRON_POST_INTERVIEW || '00 00 13 * * 0-6',
250+
// frequency of cron checking for upcoming resource bookings
251+
CRON_UPCOMING_RESOURCE_BOOKING: process.env.CRON_UPCOMING_RESOURCE_BOOKING || '00 00 13 * * 1'
230252
}

config/email_template.config.js

Lines changed: 131 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -12,93 +12,139 @@ module.exports = {
1212
* - projectName: the project name. Example: "TaaS API Misc Updates"
1313
* - reportText: the body of reported issue. Example: "I have issue with ... \n ... Thank you in advance!"
1414
*/
15-
'team-issue-report': {
16-
subject: 'Issue Reported on TaaS Team {{projectName}} ({{projectId}}).',
17-
body: 'Project Name: {{projectName}}' + '\n' +
18-
'Project ID: {{projectId}}' + '\n' +
19-
`Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' +
20-
'\n' +
21-
'{{reportText}}',
22-
recipients: config.REPORT_ISSUE_EMAILS,
23-
sendgridTemplateId: config.REPORT_ISSUE_SENDGRID_TEMPLATE_ID
24-
},
15+
teamTemplates: {
16+
'team-issue-report': {
17+
subject: 'Issue Reported on TaaS Team {{projectName}} ({{projectId}}).',
18+
body: 'Project Name: {{projectName}}' + '\n' +
19+
'Project ID: {{projectId}}' + '\n' +
20+
`Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' +
21+
'\n' +
22+
'{{reportText}}',
23+
recipients: config.REPORT_ISSUE_EMAILS,
24+
sendgridTemplateId: config.REPORT_ISSUE_SENDGRID_TEMPLATE_ID
25+
},
2526

26-
/* Report issue for a particular member
27-
*
28-
* - userHandle: the user handle. Example: "bili_2021"
29-
* - projectId: the project ID. Example: 123412
30-
* - projectName: the project name. Example: "TaaS API Misc Updates"
31-
* - reportText: the body of reported issue. Example: "I have issue with ... \n ... Thank you in advance!"
32-
*/
33-
'member-issue-report': {
34-
subject: 'Issue Reported for member {{userHandle}} on TaaS Team {{projectName}} ({{projectId}}).',
35-
body: 'User Handle: {{userHandle}}' + '\n' +
36-
'Project Name: {{projectName}}' + '\n' +
37-
'Project ID: {{projectId}}' + '\n' +
38-
`Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' +
39-
'\n' +
40-
'{{reportText}}',
41-
recipients: config.REPORT_ISSUE_EMAILS,
42-
sendgridTemplateId: config.REPORT_ISSUE_SENDGRID_TEMPLATE_ID
43-
},
27+
/* Report issue for a particular member
28+
*
29+
* - userHandle: the user handle. Example: "bili_2021"
30+
* - projectId: the project ID. Example: 123412
31+
* - projectName: the project name. Example: "TaaS API Misc Updates"
32+
* - reportText: the body of reported issue. Example: "I have issue with ... \n ... Thank you in advance!"
33+
*/
34+
'member-issue-report': {
35+
subject: 'Issue Reported for member {{userHandle}} on TaaS Team {{projectName}} ({{projectId}}).',
36+
body: 'User Handle: {{userHandle}}' + '\n' +
37+
'Project Name: {{projectName}}' + '\n' +
38+
'Project ID: {{projectId}}' + '\n' +
39+
`Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' +
40+
'\n' +
41+
'{{reportText}}',
42+
recipients: config.REPORT_ISSUE_EMAILS,
43+
sendgridTemplateId: config.REPORT_ISSUE_SENDGRID_TEMPLATE_ID
44+
},
4445

45-
/* Request extension for a particular member
46-
*
47-
* - userHandle: the user handle. Example: "bili_2021"
48-
* - projectId: the project ID. Example: 123412
49-
* - projectName: the project name. Example: "TaaS API Misc Updates"
50-
* - text: comment for the request. Example: "I would like to keep working with this member for 2 months..."
51-
*/
52-
'extension-request': {
53-
subject: 'Extension Requested for member {{userHandle}} on TaaS Team {{projectName}} ({{projectId}}).',
54-
body: 'User Handle: {{userHandle}}' + '\n' +
55-
'Project Name: {{projectName}}' + '\n' +
56-
'Project ID: {{projectId}}' + '\n' +
57-
`Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' +
58-
'\n' +
59-
'{{text}}',
60-
recipients: config.REPORT_ISSUE_EMAILS,
61-
sendgridTemplateId: config.REQUEST_EXTENSION_SENDGRID_TEMPLATE_ID
62-
},
46+
/* Request extension for a particular member
47+
*
48+
* - userHandle: the user handle. Example: "bili_2021"
49+
* - projectId: the project ID. Example: 123412
50+
* - projectName: the project name. Example: "TaaS API Misc Updates"
51+
* - text: comment for the request. Example: "I would like to keep working with this member for 2 months..."
52+
*/
53+
'extension-request': {
54+
subject: 'Extension Requested for member {{userHandle}} on TaaS Team {{projectName}} ({{projectId}}).',
55+
body: 'User Handle: {{userHandle}}' + '\n' +
56+
'Project Name: {{projectName}}' + '\n' +
57+
'Project ID: {{projectId}}' + '\n' +
58+
`Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' +
59+
'\n' +
60+
'{{text}}',
61+
recipients: config.REPORT_ISSUE_EMAILS,
62+
sendgridTemplateId: config.REQUEST_EXTENSION_SENDGRID_TEMPLATE_ID
63+
},
6364

64-
/* Request interview for a job candidate
65-
*
66-
* - interviewType: the x.ai interview type. Example: "interview-30"
67-
* - interviewRound: the round of the interview. Example: 2
68-
* - interviewDuration: duration of the interview, in minutes. Example: 30
69-
* - interviewerList: The list of interviewer email addresses. Example: "[email protected], [email protected]"
70-
* - candidateId: the id of the jobCandidate. Example: "cc562545-7b75-48bf-87e7-50b3c57e41b1"
71-
* - candidateName: Full name of candidate. Example: "John Doe"
72-
* - jobName: The title of the job. Example: "TaaS API Misc Updates"
73-
*
74-
* Template (defined in SendGrid):
75-
* Subject: '{{interviewType}} tech interview with {{candidateName}} for {{jobName}} is requested by the Customer'
76-
* Body:
77-
* 'Hello!
78-
* <br /><br />
79-
* Congratulations, you have been selected to participate in a Topcoder Gig Work Interview!
80-
* <br /><br />
81-
* Please monitor your email for a response to this where you can coordinate your availability.
82-
* <br /><br />
83-
* Interviewee: {{candidateName}}<br />
84-
* Interviewer(s): {{interviewerList}}<br />
85-
* Interview Length: {{interviewDuration}} minutes
86-
* <br /><br />
87-
* /{{interviewType}}
88-
* <br /><br />
89-
* Topcoder Info:<br />
90-
* Note: "id: {{candidateId}}, round: {{interviewRound}}"'
91-
*
92-
* Note, that the template should be defined in SendGrid.
93-
* The subject & body above (identical to actual SendGrid template) is for reference purposes.
94-
* We won't pass subject & body but only substitutions (replacements in template subject/body).
95-
*/
96-
'interview-invitation': {
97-
subject: '',
98-
body: '',
99-
from: config.INTERVIEW_INVITATION_SENDER_EMAIL,
100-
cc: config.INTERVIEW_INVITATION_CC_LIST,
101-
recipients: config.INTERVIEW_INVITATION_RECIPIENTS_LIST,
102-
sendgridTemplateId: config.INTERVIEW_INVITATION_SENDGRID_TEMPLATE_ID
65+
/* Request interview for a job candidate
66+
*
67+
* - interviewType: the x.ai interview type. Example: "interview-30"
68+
* - interviewRound: the round of the interview. Example: 2
69+
* - interviewDuration: duration of the interview, in minutes. Example: 30
70+
* - interviewerList: The list of interviewer email addresses. Example: "[email protected], [email protected]"
71+
* - candidateId: the id of the jobCandidate. Example: "cc562545-7b75-48bf-87e7-50b3c57e41b1"
72+
* - candidateName: Full name of candidate. Example: "John Doe"
73+
* - jobName: The title of the job. Example: "TaaS API Misc Updates"
74+
*
75+
* Template (defined in SendGrid):
76+
* Subject: '{{interviewType}} tech interview with {{candidateName}} for {{jobName}} is requested by the Customer'
77+
* Body:
78+
* 'Hello!
79+
* <br /><br />
80+
* Congratulations, you have been selected to participate in a Topcoder Gig Work Interview!
81+
* <br /><br />
82+
* Please monitor your email for a response to this where you can coordinate your availability.
83+
* <br /><br />
84+
* Interviewee: {{candidateName}}<br />
85+
* Interviewer(s): {{interviewerList}}<br />
86+
* Interview Length: {{interviewDuration}} minutes
87+
* <br /><br />
88+
* /{{interviewType}}
89+
* <br /><br />
90+
* Topcoder Info:<br />
91+
* Note: "id: {{candidateId}}, round: {{interviewRound}}"'
92+
*
93+
* Note, that the template should be defined in SendGrid.
94+
* The subject & body above (identical to actual SendGrid template) is for reference purposes.
95+
* We won't pass subject & body but only substitutions (replacements in template subject/body).
96+
*/
97+
'interview-invitation': {
98+
subject: '',
99+
body: '',
100+
from: config.INTERVIEW_INVITATION_SENDER_EMAIL,
101+
cc: config.INTERVIEW_INVITATION_CC_LIST,
102+
recipients: config.INTERVIEW_INVITATION_RECIPIENTS_LIST,
103+
sendgridTemplateId: config.INTERVIEW_INVITATION_SENDGRID_TEMPLATE_ID
104+
}
105+
},
106+
notificationEmailTemplates: {
107+
'taas.notification.candidates-available-for-review': {
108+
subject: 'Topcoder - {{teamName}} has job candidates available for review',
109+
body: '',
110+
recipients: [],
111+
from: config.NOTIFICATION_SENDER_EMAIL,
112+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
113+
},
114+
'taas.notification.interview-coming-up-host': {
115+
subject: 'Topcoder - Interview Coming Up: {{jobTitle}} with {{guestFullName}}',
116+
body: '',
117+
recipients: [],
118+
from: config.NOTIFICATION_SENDER_EMAIL,
119+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
120+
},
121+
'taas.notification.interview-coming-up-guest': {
122+
subject: 'Topcoder - Interview Coming Up: {{jobTitle}} with {{hostFullName}}',
123+
body: '',
124+
recipients: [],
125+
from: config.NOTIFICATION_SENDER_EMAIL,
126+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
127+
},
128+
'taas.notification.interview-awaits-resolution': {
129+
subject: 'Topcoder - Interview Awaits Resolution: {{jobTitle}} for {{guestFullName}}',
130+
body: '',
131+
recipients: [],
132+
from: config.NOTIFICATION_SENDER_EMAIL,
133+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
134+
},
135+
'taas.notification.post-interview-action-required': {
136+
subject: 'Topcoder - Candidate Action Required in {{teamName}} for {{numCandidates}} candidates',
137+
body: '',
138+
recipients: [],
139+
from: config.NOTIFICATION_SENDER_EMAIL,
140+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
141+
},
142+
'taas.notification.resource-booking-expiration': {
143+
subject: 'Topcoder - Resource Booking Expiring in {{teamName}} for {{numResourceBookings}} resource bookings',
144+
body: '',
145+
recipients: [],
146+
from: config.NOTIFICATION_SENDER_EMAIL,
147+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
148+
}
103149
}
104150
}

0 commit comments

Comments
 (0)