Commit b4eea1fa authored by nickstiffler's avatar nickstiffler Committed by Yannis Barlas
Browse files

feat(*): cron job for notifying reviewers

parent d7127c40
require('./sample')
require('./reviewer')
const moment = require('moment')
const { cron } = require('@coko/server')
const notify = require('../notify')
const {
TeamMember,
Team,
ManuscriptVersion,
Review,
} = require('@pubsweet/models')
/**
* Automates reminders to reviewers and editors when reviewers fail to take action.
* - If the reviewer hasn't accepted the invite after 5 days, another invite notification is sent.
* - If the reviewer hasn't accepted the invite after 7 days, they will be uninvited (currently just a notification to the editors telling them to un-invite).
* - If the reviewer has accepted but didn't leave a review for 3 days after acceptance, a reminder will be sent to the reviewer.
* - If the reviewer has accepted but didn't leave a review for 7 days after acceptance, they will be uninvited (again, just a notification to editors for now).
*/
const checkReviewers = async () => {
const teams = await Team.query().where({ role: 'reviewer' })
await teams.forEach(async team => {
const { objectId: versionId, objectType } = team
if (objectType !== 'manuscriptVersion') return
const version = await ManuscriptVersion.query().where({ id: versionId })
if (version.decision) {
return
}
const reviews = await Review.query().where({ articleVersionId: versionId })
if (reviews.some(review => review.status.submitted)) {
return
}
const invitedReviewers = await TeamMember.query().where({
status: 'invited',
teamId: team.id,
})
invitedReviewers.forEach(reviewer => {
const { updated, userId } = reviewer
const isAccepted = false
if (
moment()
.subtract(8, 'days')
.isAfter(moment(updated))
) {
return
}
if (
moment(updated).isBetween(
moment().subtract(6, 'days'),
moment().subtract(5, 'days'),
)
) {
notify('reviewerInvited', {
reviewerId: userId,
versionId,
})
notify('remindReviewer', { userId, isAccepted, versionId })
}
if (
moment(updated).isBetween(
moment().subtract(8, 'days'),
moment().subtract(7, 'days'),
)
) {
// TODO: Update team member to be unininvited
notify('uninviteReviewer', { userId, isAccepted, versionId })
}
})
const acceptedReviewers = await TeamMember.query().where({
status: 'acceptedInvitation',
teamId: team.id,
})
acceptedReviewers.forEach(reviewer => {
const { updated, userId } = reviewer
const isAccepted = true
if (
moment()
.subtract(8, 'days')
.isAfter(moment(updated))
) {
return
}
if (
moment(updated).isBetween(
moment().subtract(4, 'days'),
moment().subtract(3, 'days'),
)
) {
notify('remindReviewer', { userId, isAccepted, versionId })
}
if (
moment(updated).isBetween(
moment().subtract(8, 'days'),
moment().subtract(7, 'days'),
)
) {
// TODO: Update team member to be unininvited
notify('uninviteReviewer', { userId, isAccepted, versionId })
}
})
})
}
cron.schedule('0 0 * * *', () => {
checkReviewers()
})
......@@ -1155,6 +1155,78 @@ const scienceOfficerApprovalStatusChange = async context => {
sendEmail(data)
}
/*
Sends email to remindreviewer that accepted invitation but didn't leave review
*/
const remindReviewer = async context => {
const { userId, isAccepted, versionId } = context
const user = await getUserById(userId)
const name = await user.displayName
const version = await getVersionById(versionId)
let content, emails, subject
if (!isAccepted) {
const editorIds = await getAssignedOrAllEditorIds(version.manuscriptId)
emails = await getEmailsByUserIds(editorIds)
subject = 'Reviewer re-invited'
content = `
<p>Reviewer ${name} has not accepted the review invitation for '${formatManuscriptTitle(
version.title,
)}' after 5 days. The invite notification was resent.</p>
`
} else {
emails = await getEmailsByUserIds([userId])
subject = 'Reminder- review due'
content = `
<p>Dear ${name},</p>
<p>Thank you for agreeing to review the manuscript '${formatManuscriptTitle(
version.title,
)}'. This is a friendly reminder that your review is due. We look forward to receiving your review soon.</p>
<p>Best wishes,<br />
The microPublication Editorial Team</p>
`
}
const data = {
content,
subject,
to: emails,
}
sendEmail(data)
}
const uninviteReviewer = async context => {
const { userId, isAccepted, versionId } = context
const user = await getUserById(userId)
const name = await user.displayName
const version = await getVersionById(versionId)
const editorIds = await getAssignedOrAllEditorIds(version.manuscriptId)
const emails = getEmailsByUserIds(editorIds)
const acceptedMessage = isAccepted
? 'accepted the invitation to review but did not submit a review for 7 days'
: 'did not accept the invitation to review for 7 days'
const content = `
<p>Reviewer ${name} ${acceptedMessage}. They should be un-invited from '${formatManuscriptTitle(
version.title,
)}'.</p>
`
const data = {
content,
subject: 'Un-invite reviewer',
to: emails,
}
sendEmail(data)
}
const mapper = {
articleAccepted,
articleProof,
......@@ -1170,6 +1242,7 @@ const mapper = {
initialSubmission,
passwordUpdate,
reinviteReviewer,
remindReviewer,
requestResetPassword,
requestResetPasswordEmailNotFound,
reviewerInvitationResponse,
......@@ -1177,6 +1250,7 @@ const mapper = {
reviewSubmitted,
revisionSubmitted,
scienceOfficerApprovalStatusChange,
uninviteReviewer,
}
const email = async (type, context) => {
......
......@@ -19,6 +19,7 @@ const mapper = {
initialSubmission: ['email'],
passwordUpdate: ['email'],
reinviteReviewer: ['email'],
remindReviewer: ['email'],
requestResetPassword: ['email'],
requestResetPasswordEmailNotFound: ['email'],
reviewerInvitationResponse: ['email'],
......@@ -26,6 +27,7 @@ const mapper = {
reviewSubmitted: ['email'],
revisionSubmitted: ['email'],
scienceOfficerApprovalStatusChange: ['email'],
uninviteReviewer: ['email'],
}
const runType = (type, context) => {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment