diff --git a/config/default.js b/config/default.js index 6a8cfef3..e3dc3cf8 100644 --- a/config/default.js +++ b/config/default.js @@ -250,7 +250,7 @@ module.exports = { offered: 'withdrawn' }, // the sender email - NOTIFICATION_SENDER_EMAIL: process.env.NOTIFICATION_SENDER_EMAIL, + NOTIFICATION_SENDER_EMAIL: process.env.NOTIFICATION_SENDER_EMAIL || 'noreply@topcoder.com', // the email notification sendgrid template id NOTIFICATION_SENDGRID_TEMPLATE_ID: process.env.NOTIFICATION_SENDGRID_TEMPLATE_ID, // frequency of cron checking for available candidates for review @@ -275,6 +275,8 @@ module.exports = { INTERVIEW_COMPLETED_PAST_TIME: process.env.INTERVIEW_COMPLETED_PAST_TIME || 'PT4H', // The time before resource booking expiry when we should start sending notifications RESOURCE_BOOKING_EXPIRY_TIME: process.env.RESOURCE_BOOKING_EXPIRY_TIME || 'P21D', + // The match window for fetching post interview actions + POST_INTERVIEW_ACTION_MATCH_WINDOW: process.env.POST_INTERVIEW_ACTION_MATCH_WINDOW || 'P1D', // The Stripe STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, CURRENCY: process.env.CURRENCY || 'usd', diff --git a/scripts/demo-email-notifications/index.js b/scripts/demo-email-notifications/index.js index 9a792b26..7e5b19c0 100644 --- a/scripts/demo-email-notifications/index.js +++ b/scripts/demo-email-notifications/index.js @@ -42,7 +42,11 @@ async function resetNotificationRecords () { const jobCandidate = await JobCandidate.findById('881a19de-2b0c-4bb9-b36a-4cb5e223bdb5') await jobCandidate.update({ status: 'interview' }) const c2Interview = await Interview.findById('077aa2ca-5b60-4ad9-a965-1b37e08a5046') - await c2Interview.update({ startTimestamp: completedStartTimestamp, duration, endTimestamp, guestNames: ['guest1', 'guest2'], hostName: 'hostName' }) + await c2Interview.update({ startTimestamp: moment().subtract(moment.duration(config.POST_INTERVIEW_ACTION_MATCH_WINDOW)).subtract(30, 'm').toDate(), duration, endTimestamp, guestNames: ['guest1', 'guest2'], hostName: 'hostName' }) + const jobCandidateWithinOneDay = await JobCandidate.findById('827ee401-df04-42e1-abbe-7b97ce7937ff') + await jobCandidateWithinOneDay.update({ status: 'interview' }) + const interviewWithinOneDay = await Interview.findById('3144fa65-ea1a-4bec-81b0-7cb1c8845826') + await interviewWithinOneDay.update({ startTimestamp: completedStartTimestamp, duration, endTimestamp, guestNames: ['guest1', 'guest2'], hostName: 'hostName' }) // reset upcoming resource booking expiration records localLogger.info('reset upcoming resource booking expiration records') diff --git a/src/common/helper.js b/src/common/helper.js index 05ed2c5c..4d2f1dab 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -1038,7 +1038,7 @@ async function getProjects (currentUser, criteria = {}) { message: `response body: ${JSON.stringify(res.body)}` }) const result = _.map(res.body, (item) => { - return _.pick(item, ['id', 'name', 'invites', 'members']) + return _.extend(_.pick(item, ['id', 'invites', 'members']), { name: _.unescape(item.name) }) }) return { total: Number(_.get(res.headers, 'x-total')), @@ -1195,7 +1195,7 @@ async function getProjectById (currentUser, id) { context: 'getProjectById', message: `response body: ${JSON.stringify(res.body)}` }) - return _.pick(res.body, ['id', 'name', 'invites', 'members']) + return _.extend(_.pick(res.body, ['id', 'invites', 'members']), { name: _.unescape(res.body.name) }) } catch (err) { if (err.status === HttpStatus.FORBIDDEN) { throw new errors.ForbiddenError( @@ -1925,7 +1925,8 @@ async function createProject (currentUser, data) { context: 'createProject', message: `response body: ${JSON.stringify(res)}` }) - return _.get(res, 'body') + const result = _.get(res, 'body') + return _.extend(result, { name: _.unescape(_.get(result, 'name')) }) } /** @@ -2062,6 +2063,8 @@ function formatDateTimeEDT (date) { function formatDateEDT (date) { if (date) { return moment(date).tz('America/New_York').format('MMM D, YYYY') + } else { + return 'TBD' } } diff --git a/src/eventHandlers/TeamEventHandler.js b/src/eventHandlers/TeamEventHandler.js index aeec9e25..985ffc61 100644 --- a/src/eventHandlers/TeamEventHandler.js +++ b/src/eventHandlers/TeamEventHandler.js @@ -49,7 +49,7 @@ async function sendNotificationEmail (payload) { logger.debug({ component: 'TeamEventHandler', context: 'sendNotificationEmail', - message: `project id: ${payload.project.id} created with jobs: ${_.join(_.map(payload.jobs, 'id'), ',')}` + message: `project id: ${payload.project.id}, subject: ${data.subject}, created with jobs: ${_.join(_.map(payload.jobs, 'id'), ',')}` }) } diff --git a/src/services/NotificationsSchedulerService.js b/src/services/NotificationsSchedulerService.js index d90c77b6..133553d1 100644 --- a/src/services/NotificationsSchedulerService.js +++ b/src/services/NotificationsSchedulerService.js @@ -109,6 +109,7 @@ async function getDataForInterview (interview, jobCandidate, job) { * Sends notifications to all the teams which have candidates available for review */ async function sendCandidatesAvailableNotifications () { + localLogger.debug('[sendCandidatesAvailableNotifications]: Looking for due records...') const jobsDao = await Job.findAll({ include: [{ model: JobCandidate, @@ -190,6 +191,7 @@ async function sendCandidatesAvailableNotifications () { * Sends reminders to the hosts and guests about their upcoming interview(s) */ async function sendInterviewComingUpNotifications () { + localLogger.debug('[sendInterviewComingUpNotifications]: Looking for due records...') const currentTime = moment.utc() const timestampFilter = { [Op.or]: [] @@ -239,7 +241,7 @@ async function sendInterviewComingUpNotifications () { if (!_.isEmpty(interview.hostEmail)) { sendNotification({}, { template: 'taas.notification.interview-coming-up-host', - recipients: [interview.hostEmail], + recipients: [{ email: interview.hostEmail }], data: { ...data, notificationType: { @@ -258,7 +260,7 @@ async function sendInterviewComingUpNotifications () { // send guest emails sendNotification({}, { template: 'taas.notification.interview-coming-up-guest', - recipients: interview.guestEmails, + recipients: interview.guestEmails.map((email) => ({ email })), data: { ...data, notificationType: { @@ -281,6 +283,7 @@ async function sendInterviewComingUpNotifications () { * Sends reminder to the interview host after it ends to change the interview status */ async function sendInterviewCompletedNotifications () { + localLogger.debug('[sendInterviewCompletedNotifications]: Looking for due records...') const window = moment.duration(config.INTERVIEW_COMPLETED_MATCH_WINDOW) const rangeStart = moment.utc().subtract(moment.duration(config.INTERVIEW_COMPLETED_PAST_TIME)) const rangeEnd = rangeStart.clone().add(window) @@ -323,7 +326,7 @@ async function sendInterviewCompletedNotifications () { sendNotification({}, { template: 'taas.notification.interview-awaits-resolution', - recipients: [interview.hostEmail], + recipients: [{ email: interview.hostEmail }], data: { ...data, notificationType: { @@ -344,6 +347,7 @@ async function sendInterviewCompletedNotifications () { * to update the job candidate status */ async function sendPostInterviewActionNotifications () { + localLogger.debug('[sendPostInterviewActionNotifications]: Looking for due records...') const completedJobCandidates = await JobCandidate.findAll({ where: { status: constants.JobCandidateStatus.INTERVIEW @@ -353,7 +357,10 @@ async function sendPostInterviewActionNotifications () { as: 'interviews', required: true, where: { - status: constants.Interviews.Status.Completed + status: constants.Interviews.Status.Completed, + startTimestamp: { + [Op.lte]: moment.utc().subtract(moment.duration(config.POST_INTERVIEW_ACTION_MATCH_WINDOW)) + } } }] }) @@ -436,6 +443,7 @@ async function sendPostInterviewActionNotifications () { * Sends reminders to all members of teams which have atleast one upcoming resource booking expiration */ async function sendResourceBookingExpirationNotifications () { + localLogger.debug('[sendResourceBookingExpirationNotifications]: Looking for due records...') const currentTime = moment.utc() const maxEndDate = currentTime.clone().add(moment.duration(config.RESOURCE_BOOKING_EXPIRY_TIME)) @@ -543,7 +551,7 @@ async function sendResourceBookingExpirationNotifications () { async function sendNotification (currentUser, data, webNotifications = []) { const template = emailTemplates[data.template] const dataCC = data.cc || [] - const templateCC = template.cc || [] + const templateCC = (template.cc || []).map(email => ({ email })) const dataRecipients = data.recipients || [] const templateRecipients = (template.recipients || []).map(email => ({ email })) const subjectBody = { @@ -557,14 +565,14 @@ async function sendNotification (currentUser, data, webNotifications = []) { ) } - const recipients = _.map(_.uniq([...dataRecipients, ...templateRecipients]), function (r) { return { email: r } }) + const recipients = _.uniq([...dataRecipients, ...templateRecipients]) const emailData = { serviceId: 'email', type: data.template, details: { from: data.from || template.from, recipients, - cc: _.map(_.uniq([...dataCC, ...templateCC]), function (r) { return { email: r } }), + cc: _.uniq([...dataCC, ...templateCC]), data: { ...data.data, ...subjectBody }, sendgridTemplateId: template.sendgridTemplateId, version: 'v3'