Skip to content

Commit 07e9218

Browse files
authored
Merge pull request #449 from topcoder-platform/feature/notifications-scheduler
[DEV] Send Email Notifications using Scheduler
2 parents e480323 + fa93c02 commit 07e9218

File tree

16 files changed

+1564
-105
lines changed

16 files changed

+1564
-105
lines changed

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
@@ -15,6 +15,7 @@ const eventHandlers = require('./src/eventHandlers')
1515
const interviewService = require('./src/services/InterviewService')
1616
const { processScheduler } = require('./src/services/PaymentSchedulerService')
1717
const { sendSurveys } = require('./src/services/SurveyService')
18+
const emailNotificationService = require('./src/services/EmailNotificationService')
1819

1920
// setup express app
2021
const app = express()
@@ -103,6 +104,12 @@ const server = app.listen(app.get('port'), () => {
103104
schedule.scheduleJob(config.WEEKLY_SURVEY.CRON, sendSurveys)
104105
// schedule payment processing
105106
schedule.scheduleJob(config.PAYMENT_PROCESSING.CRON, processScheduler)
107+
108+
schedule.scheduleJob(config.CRON_CANDIDATE_REVIEW, emailNotificationService.sendCandidatesAvailableEmails)
109+
schedule.scheduleJob(config.CRON_INTERVIEW_COMING_UP, emailNotificationService.sendInterviewComingUpEmails)
110+
schedule.scheduleJob(config.CRON_INTERVIEW_COMPLETED, emailNotificationService.sendInterviewCompletedEmails)
111+
schedule.scheduleJob(config.CRON_POST_INTERVIEW, emailNotificationService.sendPostInterviewActionEmails)
112+
schedule.scheduleJob(config.CRON_UPCOMING_RESOURCE_BOOKING, emailNotificationService.sendResourceBookingExpirationEmails)
106113
})
107114

108115
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(','),
@@ -237,5 +239,25 @@ module.exports = {
237239
interview: 'withdrawn',
238240
selected: 'withdrawn',
239241
offered: 'withdrawn'
240-
}
242+
},
243+
// the sender email
244+
NOTIFICATION_SENDER_EMAIL: process.env.NOTIFICATION_SENDER_EMAIL,
245+
// the email notification sendgrid template id
246+
NOTIFICATION_SENDGRID_TEMPLATE_ID: process.env.NOTIFICATION_SENDGRID_TEMPLATE_ID,
247+
// hours after interview completed when we should post the notification
248+
INTERVIEW_COMPLETED_NOTIFICATION_HOURS: process.env.INTERVIEW_COMPLETED_NOTIFICATION_HOURS || 4,
249+
// no of weeks before expiry when we should post the notification
250+
RESOURCE_BOOKING_EXPIRY_NOTIFICATION_WEEKS: process.env.RESOURCE_BOOKING_EXPIRY_NOTIFICATION_WEEKS || 3,
251+
// frequency of cron checking for available candidates for review
252+
CRON_CANDIDATE_REVIEW: process.env.CRON_CANDIDATE_REVIEW || '00 00 13 * * 0-6',
253+
// frequency of cron checking for coming up interviews
254+
// when changing this to frequency other than 5 mins, please change the minutesRange in sendInterviewComingUpEmails correspondingly
255+
CRON_INTERVIEW_COMING_UP: process.env.CRON_INTERVIEW_COMING_UP || '*/5 * * * *',
256+
// frequency of cron checking for interview completed
257+
// when changing this to frequency other than 5 mins, please change the minutesRange in sendInterviewCompletedEmails correspondingly
258+
CRON_INTERVIEW_COMPLETED: process.env.CRON_INTERVIEW_COMPLETED || '*/5 * * * *',
259+
// frequency of cron checking for post interview actions
260+
CRON_POST_INTERVIEW: process.env.CRON_POST_INTERVIEW || '00 00 13 * * 0-6',
261+
// frequency of cron checking for upcoming resource bookings
262+
CRON_UPCOMING_RESOURCE_BOOKING: process.env.CRON_UPCOMING_RESOURCE_BOOKING || '00 00 13 * * 1'
241263
}

config/email_template.config.js

Lines changed: 141 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -6,99 +6,152 @@
66
const config = require('config')
77

88
module.exports = {
9-
/* Report a general issue for a team.
10-
*
11-
* - projectId: the project ID. Example: 123412
12-
* - projectName: the project name. Example: "TaaS API Misc Updates"
13-
* - reportText: the body of reported issue. Example: "I have issue with ... \n ... Thank you in advance!"
9+
/**
10+
* List all the kind of emails which could be sent by the endpoint `POST /taas-teams/email` inside `teamTemplates`.
1411
*/
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-
},
12+
teamTemplates: {
13+
/* Report a general issue for a team.
14+
*
15+
* - projectId: the project ID. Example: 123412
16+
* - projectName: the project name. Example: "TaaS API Misc Updates"
17+
* - reportText: the body of reported issue. Example: "I have issue with ... \n ... Thank you in advance!"
18+
*/
19+
'team-issue-report': {
20+
subject: 'Issue Reported on TaaS Team {{projectName}} ({{projectId}}).',
21+
body: 'Project Name: {{projectName}}' + '\n' +
22+
'Project ID: {{projectId}}' + '\n' +
23+
`Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' +
24+
'\n' +
25+
'{{reportText}}',
26+
recipients: config.REPORT_ISSUE_EMAILS,
27+
sendgridTemplateId: config.REPORT_ISSUE_SENDGRID_TEMPLATE_ID
28+
},
2529

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

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
49+
/* Request extension for a particular member
50+
*
51+
* - userHandle: the user handle. Example: "bili_2021"
52+
* - projectId: the project ID. Example: 123412
53+
* - projectName: the project name. Example: "TaaS API Misc Updates"
54+
* - text: comment for the request. Example: "I would like to keep working with this member for 2 months..."
55+
*/
56+
'extension-request': {
57+
subject: 'Extension Requested for member {{userHandle}} on TaaS Team {{projectName}} ({{projectId}}).',
58+
body: 'User Handle: {{userHandle}}' + '\n' +
59+
'Project Name: {{projectName}}' + '\n' +
60+
'Project ID: {{projectId}}' + '\n' +
61+
`Project URL: ${config.TAAS_APP_URL}/{{projectId}}` + '\n' +
62+
'\n' +
63+
'{{text}}',
64+
recipients: config.REPORT_ISSUE_EMAILS,
65+
sendgridTemplateId: config.REQUEST_EXTENSION_SENDGRID_TEMPLATE_ID
66+
},
67+
68+
/* Request interview for a job candidate
69+
*
70+
* - interviewType: the x.ai interview type. Example: "interview-30"
71+
* - interviewRound: the round of the interview. Example: 2
72+
* - interviewDuration: duration of the interview, in minutes. Example: 30
73+
* - interviewerList: The list of interviewer email addresses. Example: "first@attendee.com, second@attendee.com"
74+
* - candidateId: the id of the jobCandidate. Example: "cc562545-7b75-48bf-87e7-50b3c57e41b1"
75+
* - candidateName: Full name of candidate. Example: "John Doe"
76+
* - jobName: The title of the job. Example: "TaaS API Misc Updates"
77+
*
78+
* Template (defined in SendGrid):
79+
* Subject: '{{interviewType}} tech interview with {{candidateName}} for {{jobName}} is requested by the Customer'
80+
* Body:
81+
* 'Hello!
82+
* <br /><br />
83+
* Congratulations, you have been selected to participate in a Topcoder Gig Work Interview!
84+
* <br /><br />
85+
* Please monitor your email for a response to this where you can coordinate your availability.
86+
* <br /><br />
87+
* Interviewee: {{candidateName}}<br />
88+
* Interviewer(s): {{interviewerList}}<br />
89+
* Interview Length: {{interviewDuration}} minutes
90+
* <br /><br />
91+
* /{{interviewType}}
92+
* <br /><br />
93+
* Topcoder Info:<br />
94+
* Note: "id: {{candidateId}}, round: {{interviewRound}}"'
95+
*
96+
* Note, that the template should be defined in SendGrid.
97+
* The subject & body above (identical to actual SendGrid template) is for reference purposes.
98+
* We won't pass subject & body but only substitutions (replacements in template subject/body).
99+
*/
100+
'interview-invitation': {
101+
subject: '',
102+
body: '',
103+
from: config.INTERVIEW_INVITATION_SENDER_EMAIL,
104+
cc: config.INTERVIEW_INVITATION_CC_LIST,
105+
recipients: config.INTERVIEW_INVITATION_RECIPIENTS_LIST,
106+
sendgridTemplateId: config.INTERVIEW_INVITATION_SENDGRID_TEMPLATE_ID
107+
}
62108
},
63109

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: "first@attendee.com, second@attendee.com"
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).
110+
/**
111+
* List all kind of emails which could be send as Email Notifications by scheduler, API endpoints or anything else.
95112
*/
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
113+
notificationEmailTemplates: {
114+
'taas.notification.candidates-available-for-review': {
115+
subject: 'Topcoder - {{teamName}} has job candidates available for review',
116+
body: '',
117+
recipients: [],
118+
from: config.NOTIFICATION_SENDER_EMAIL,
119+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
120+
},
121+
'taas.notification.interview-coming-up-host': {
122+
subject: 'Topcoder - Interview Coming Up: {{jobTitle}} with {{guestFullName}}',
123+
body: '',
124+
recipients: [],
125+
from: config.NOTIFICATION_SENDER_EMAIL,
126+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
127+
},
128+
'taas.notification.interview-coming-up-guest': {
129+
subject: 'Topcoder - Interview Coming Up: {{jobTitle}} with {{hostFullName}}',
130+
body: '',
131+
recipients: [],
132+
from: config.NOTIFICATION_SENDER_EMAIL,
133+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
134+
},
135+
'taas.notification.interview-awaits-resolution': {
136+
subject: 'Topcoder - Interview Awaits Resolution: {{jobTitle}} for {{guestFullName}}',
137+
body: '',
138+
recipients: [],
139+
from: config.NOTIFICATION_SENDER_EMAIL,
140+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
141+
},
142+
'taas.notification.post-interview-action-required': {
143+
subject: 'Topcoder - Candidate Action Required in {{teamName}} for {{numCandidates}} candidates',
144+
body: '',
145+
recipients: [],
146+
from: config.NOTIFICATION_SENDER_EMAIL,
147+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
148+
},
149+
'taas.notification.resource-booking-expiration': {
150+
subject: 'Topcoder - Resource Booking Expiring in {{teamName}} for {{numResourceBookings}} resource bookings',
151+
body: '',
152+
recipients: [],
153+
from: config.NOTIFICATION_SENDER_EMAIL,
154+
sendgridTemplateId: config.NOTIFICATION_SENDGRID_TEMPLATE_ID
155+
}
103156
}
104157
}

0 commit comments

Comments
 (0)