Skip to content
Snippets Groups Projects
Commit 5ef43cc1 authored by Alexandru Munteanu's avatar Alexandru Munteanu
Browse files

Merge branch 'develop' of gitlab.coko.foundation:xpub/xpub-faraday into develop

parents 68fa0dc5 30854908
No related branches found
No related tags found
1 merge request!6Agree/Decline to work on a manuscript
...@@ -2,6 +2,7 @@ const invitationHelper = require('./Invitation') ...@@ -2,6 +2,7 @@ const invitationHelper = require('./Invitation')
const mailService = require('pubsweet-component-mail-service') const mailService = require('pubsweet-component-mail-service')
const logger = require('@pubsweet/logger') const logger = require('@pubsweet/logger')
const config = require('config') const config = require('config')
const last = require('lodash/last')
const statuses = config.get('statuses') const statuses = config.get('statuses')
module.exports = { module.exports = {
...@@ -62,4 +63,27 @@ module.exports = { ...@@ -62,4 +63,27 @@ module.exports = {
collection.visibleStatus = statuses[collection.status].private collection.visibleStatus = statuses[collection.status].private
await collection.save() await collection.save()
}, },
getFragmentAndAuthorData: async (models, collection) => {
const fragment = await models.Fragment.find(last(collection.fragments))
let { title } = fragment.metadata
title = title.replace(/<(.|\n)*?>/g, '')
const submittingAuthorData = collection.authors.find(
author => author.isSubmitting === true,
)
const author = await models.User.find(submittingAuthorData.userId)
const authorName = `${author.firstName} ${author.lastName}`
const { id } = fragment
return { title, authorName, id }
},
updateReviewerCollectionStatus: async collection => {
const reviewerInvitations = collection.invitations.filter(
inv => inv.role === 'reviewer',
)
if (reviewerInvitations.length === 0) {
collection.status = 'heAssigned'
collection.visibleStatus = statuses[collection.status].private
}
await collection.save()
},
} }
const helpers = require('./helpers') const helpers = require('./helpers')
const mailService = require('pubsweet-component-mail-service') const mailService = require('pubsweet-component-mail-service')
const logger = require('@pubsweet/logger') const logger = require('@pubsweet/logger')
const collectionHelper = require('./Collection')
module.exports = { const setupNewUser = async (
setupNewUser: async ( body,
body, url,
url, res,
res, email,
role,
UserModel,
invitationType,
) => {
const { firstName, lastName, affiliation, title } = body
const newUser = await helpers.createNewUser(
email, email,
role, firstName,
lastName,
affiliation,
title,
UserModel, UserModel,
invitationType, role,
) => { )
const { firstName, lastName, affiliation, title } = body
const newUser = await helpers.createNewUser( try {
email, if (role !== 'reviewer') {
firstName, await mailService.setupInviteEmail(newUser, invitationType, url)
lastName, }
affiliation,
return newUser
} catch (e) {
logger.error(e.message)
return { status: 500, error: 'Email could not be sent.' }
}
}
const getEditorInChief = async UserModel => {
const users = await UserModel.all()
const eic = users.find(user => user.editorInChief === true)
return eic
}
const setupReviewerDeclinedEmailData = async (
models,
collection,
req,
res,
user,
mailService,
) => {
const {
title,
authorName,
id,
} = await collectionHelper.getFragmentAndAuthorData(models, collection)
const eic = await getEditorInChief(models.User)
const toEmail = collection.handlingEditor.email
try {
await mailService.setupReviewerDeclinedEmail(
toEmail,
user,
collection,
title, title,
UserModel, authorName,
role, id,
`${eic.firstName} ${eic.lastName}`,
`${req.protocol}://${req.get('host')}`,
) )
await user.save()
res.status(200).json({})
} catch (e) {
logger.error(e)
return res.status(500).json({ error: 'Email could not be sent.' })
}
}
try { const setupReviewerUnassignEmail = async (
if (role !== 'reviewer') { models,
await mailService.setupInviteEmail(newUser, invitationType, url) collection,
} user,
mailService,
) => {
const { title, authorName } = await collectionHelper.getFragmentAndAuthorData(
models,
collection,
)
return newUser await mailService.setupReviewerUnassignEmail(
} catch (e) { user,
logger.error(e.message) collection,
return { status: 500, error: 'Email could not be sent.' } title,
} authorName,
}, )
getEditorInChief: async UserModel => { }
const users = await UserModel.all()
const eic = users.find(user => user.editorInChief === true) module.exports = {
return eic setupNewUser,
}, getEditorInChief,
setupReviewerDeclinedEmailData,
setupReviewerUnassignEmail,
} }
const logger = require('@pubsweet/logger')
const helpers = require('../../helpers/helpers') const helpers = require('../../helpers/helpers')
const teamHelper = require('../../helpers/Team') const teamHelper = require('../../helpers/Team')
const mailService = require('pubsweet-component-mail-service') const mailService = require('pubsweet-component-mail-service')
const userHelper = require('../../helpers/User')
module.exports = models => async (req, res) => { module.exports = models => async (req, res) => {
const { collectionId, invitationId } = req.params const { collectionId, invitationId } = req.params
...@@ -36,7 +36,6 @@ module.exports = models => async (req, res) => { ...@@ -36,7 +36,6 @@ module.exports = models => async (req, res) => {
invitation.timestamp = Date.now() invitation.timestamp = Date.now()
invitation.hasAnswer = true invitation.hasAnswer = true
const toEmail = collection.handlingEditor.email
invitation.isAccepted = false invitation.isAccepted = false
const team = await teamHelper.getTeamByGroupAndCollection( const team = await teamHelper.getTeamByGroupAndCollection(
collectionId, collectionId,
...@@ -46,30 +45,14 @@ module.exports = models => async (req, res) => { ...@@ -46,30 +45,14 @@ module.exports = models => async (req, res) => {
await collection.save() await collection.save()
await teamHelper.removeTeamMember(team.id, user.id, models.Team) await teamHelper.removeTeamMember(team.id, user.id, models.Team)
user.teams = user.teams.filter(userTeamId => team.id !== userTeamId) user.teams = user.teams.filter(userTeamId => team.id !== userTeamId)
const fragment = await models.Fragment.find(collection.fragments[0]) return await userHelper.setupReviewerDeclinedEmailData(
let { title } = fragment.metadata models,
title = title.replace(/<(.|\n)*?>/g, '') collection,
req,
const submittingAuthorData = collection.authors.find( res,
author => author.isSubmitting === true, user,
mailService,
) )
const author = await models.User.find(submittingAuthorData.userId)
try {
await mailService.setupReviewerDeclineEmail(
toEmail,
user,
collection,
title,
`${author.firstName} ${author.lastName}`,
)
} catch (e) {
logger.error(e)
return res.status(500).json({ error: 'Email could not be sent.' })
}
await user.save()
res.status(200).json({})
return
} catch (e) { } catch (e) {
const notFoundError = await helpers.handleNotFoundError(e, 'item') const notFoundError = await helpers.handleNotFoundError(e, 'item')
return res.status(notFoundError.status).json({ return res.status(notFoundError.status).json({
......
...@@ -3,6 +3,8 @@ const teamHelper = require('../../helpers/Team') ...@@ -3,6 +3,8 @@ const teamHelper = require('../../helpers/Team')
const mailService = require('pubsweet-component-mail-service') const mailService = require('pubsweet-component-mail-service')
const logger = require('@pubsweet/logger') const logger = require('@pubsweet/logger')
const config = require('config') const config = require('config')
const userHelper = require('../../helpers/User')
const collectionHelper = require('../../helpers/Collection')
const statuses = config.get('statuses') const statuses = config.get('statuses')
module.exports = models => async (req, res) => { module.exports = models => async (req, res) => {
...@@ -34,13 +36,7 @@ module.exports = models => async (req, res) => { ...@@ -34,13 +36,7 @@ module.exports = models => async (req, res) => {
collection.visibleStatus = statuses[collection.status].private collection.visibleStatus = statuses[collection.status].private
delete collection.handlingEditor delete collection.handlingEditor
} else if (invitation.role === 'reviewer') { } else if (invitation.role === 'reviewer') {
const reviewerInvitations = collection.invitations.filter( await collectionHelper.updateReviewerCollectionStatus(collection)
inv => inv.role === 'reviewer',
)
if (reviewerInvitations.length === 0) {
collection.status = 'heAssigned'
collection.visibleStatus = statuses[collection.status].private
}
} }
await collection.save() await collection.save()
await teamHelper.removeTeamMember(team.id, invitation.userId, models.Team) await teamHelper.removeTeamMember(team.id, invitation.userId, models.Team)
...@@ -48,20 +44,19 @@ module.exports = models => async (req, res) => { ...@@ -48,20 +44,19 @@ module.exports = models => async (req, res) => {
user.teams = user.teams.filter(userTeamId => team.id !== userTeamId) user.teams = user.teams.filter(userTeamId => team.id !== userTeamId)
await user.save() await user.save()
try { try {
let emailType if (invitation.role === 'handlingEditor') {
switch (invitation.role) { await mailService.setupRevokeInvitationEmail(
case 'handlingEditor': user.email,
emailType = 'revoke-handling-editor' 'revoke-handling-editor',
break )
case 'reviewer': } else if (invitation.role === 'reviewer') {
emailType = 'unassign-reviewer' await userHelper.setupReviewerUnassignEmail(
break models,
default: collection,
return res.status(500).json({ user,
error: 'Something went wrong', mailService,
}) )
} }
await mailService.setupRevokeInvitationEmail(user.email, emailType)
return res.status(200).json({}) return res.status(200).json({})
} catch (e) { } catch (e) {
......
...@@ -4,7 +4,9 @@ const teamHelper = require('../../helpers/Team') ...@@ -4,7 +4,9 @@ const teamHelper = require('../../helpers/Team')
const mailService = require('pubsweet-component-mail-service') const mailService = require('pubsweet-component-mail-service')
const userHelper = require('../../helpers/User') const userHelper = require('../../helpers/User')
const collectionHelper = require('../../helpers/Collection') const collectionHelper = require('../../helpers/Collection')
const config = require('config')
const statuses = config.get('statuses')
module.exports = models => async (req, res) => { module.exports = models => async (req, res) => {
const { collectionId, invitationId } = req.params const { collectionId, invitationId } = req.params
const { isAccepted, reason } = req.body const { isAccepted, reason } = req.body
...@@ -17,7 +19,7 @@ module.exports = models => async (req, res) => { ...@@ -17,7 +19,7 @@ module.exports = models => async (req, res) => {
let user = await models.User.find(req.user) let user = await models.User.find(req.user)
try { try {
const collection = await models.Collection.find(collectionId) let collection = await models.Collection.find(collectionId)
const invitation = await collection.invitations.find( const invitation = await collection.invitations.find(
invitation => invitation.id === invitationId, invitation => invitation.id === invitationId,
) )
...@@ -46,6 +48,13 @@ module.exports = models => async (req, res) => { ...@@ -46,6 +48,13 @@ module.exports = models => async (req, res) => {
toEmail = collection.handlingEditor.email toEmail = collection.handlingEditor.email
if (isAccepted === true) { if (isAccepted === true) {
invitation.isAccepted = true invitation.isAccepted = true
if (
invitation.role === 'reviewer' &&
collection.status === 'reviewersInvited'
) {
collection.status = 'underReview'
collection.visibleStatus = statuses[collection.status].private
}
await collection.save() await collection.save()
try { try {
await mailService.setupAgreeEmail( await mailService.setupAgreeEmail(
...@@ -85,20 +94,14 @@ module.exports = models => async (req, res) => { ...@@ -85,20 +94,14 @@ module.exports = models => async (req, res) => {
reason, reason,
) )
} else if (invitation.role === 'reviewer') { } else if (invitation.role === 'reviewer') {
const fragment = await models.Fragment.find(collection.fragments[0]) await collectionHelper.updateReviewerCollectionStatus(collection)
let { title } = fragment.metadata return await userHelper.setupReviewerDeclinedEmailData(
title = title.replace(/<(.|\n)*?>/g, '') models,
const submittingAuthorData = collection.authors.find(
author => author.isSubmitting === true,
)
const author = await models.User.find(submittingAuthorData.userId)
await mailService.setupReviewerDeclineEmail(
toEmail,
user,
collection, collection,
title, req,
`${author.firstName} ${author.lastName}`, res,
user,
mailService,
) )
} }
} catch (e) { } catch (e) {
......
...@@ -10,6 +10,7 @@ jest.mock('pubsweet-component-mail-service', () => ({ ...@@ -10,6 +10,7 @@ jest.mock('pubsweet-component-mail-service', () => ({
setupAssignEmail: jest.fn(), setupAssignEmail: jest.fn(),
setupAgreeEmail: jest.fn(), setupAgreeEmail: jest.fn(),
setupDeclineEmail: jest.fn(), setupDeclineEmail: jest.fn(),
setupReviewerDeclinedEmail: jest.fn(),
})) }))
const reqBody = { const reqBody = {
......
...@@ -199,8 +199,7 @@ module.exports = { ...@@ -199,8 +199,7 @@ module.exports = {
} }
return Email.send(mailData) return Email.send(mailData)
}, },
setupReviewerDeclineEmail: async ( setupReviewerUnassignEmail: async (
toEmail,
invitedUser, invitedUser,
collection, collection,
title, title,
...@@ -229,6 +228,42 @@ module.exports = { ...@@ -229,6 +228,42 @@ module.exports = {
} }
return Email.send(mailData) return Email.send(mailData)
}, },
setupReviewerDeclinedEmail: async (
toEmail,
invitedUser,
collection,
title,
authorName,
fragmentId,
eicName,
baseUrl,
) => {
const subject = `${collection.customId}: Manuscript Reviews`
const manuscriptUrl = `${baseUrl}/projects/${
collection.id
}/versions/${fragmentId}/details`
const replacements = {
reviewerName: `${invitedUser.firstName} ${invitedUser.lastName}`,
title,
editorName: collection.handlingEditor.name,
authorName,
manuscriptUrl,
eicName,
}
const { htmlBody, textBody } = getEmailBody(
'reviewer-declined',
replacements,
)
const mailData = {
from: config.get('mailer.from'),
to: toEmail,
subject,
text: textBody,
html: htmlBody,
}
return Email.send(mailData)
},
} }
const getEmailBody = (emailType, replacements) => { const getEmailBody = (emailType, replacements) => {
......
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" />
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<!--<![endif]-->
<!--[if (gte mso 9)|(IE)]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if (gte mso 9)|(IE)]>
<style type="text/css">
body {width: 600px;margin: 0 auto;}
table {border-collapse: collapse;}
table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;}
img {-ms-interpolation-mode: bicubic;}
</style>
<![endif]-->
<style type="text/css">
body,
p,
div {
font-family: helvetica, arial, sans-serif;
font-size: 14px;
}
body {
color: #626262;
}
body a {
color: #0D78F2;
text-decoration: none;
}
p {
margin: 0;
padding: 0;
}
table.wrapper {
width: 100% !important;
table-layout: fixed;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
img.max-width {
max-width: 100% !important;
}
.column.of-2 {
width: 50%;
}
.column.of-3 {
width: 33.333%;
}
.column.of-4 {
width: 25%;
}
@media screen and (max-width:480px) {
.preheader .rightColumnContent,
.footer .rightColumnContent {
text-align: left !important;
}
.preheader .rightColumnContent div,
.preheader .rightColumnContent span,
.footer .rightColumnContent div,
.footer .rightColumnContent span {
text-align: left !important;
}
.preheader .rightColumnContent,
.preheader .leftColumnContent {
font-size: 80% !important;
padding: 5px 0;
}
table.wrapper-mobile {
width: 100% !important;
table-layout: fixed;
}
img.max-width {
height: auto !important;
max-width: 480px !important;
}
a.bulletproof-button {
display: block !important;
width: auto !important;
font-size: 80%;
padding-left: 0 !important;
padding-right: 0 !important;
}
.columns {
width: 100% !important;
}
.column {
display: block !important;
width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
}
</style>
<!--user entered Head Start-->
<!--End Head user entered-->
</head>
<body>
<center class="wrapper" data-link-color="#0D78F2" data-body-style="font-size: 14px; font-family: helvetica,arial,sans-serif; color: #626262; background-color: #F4F4F4;">
<div class="webkit">
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#F4F4F4">
<tr>
<td valign="top" bgcolor="#F4F4F4" width="100%">
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="100%">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<!--[if mso]>
<center>
<table><tr><td width="600">
<![endif]-->
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width:600px;" align="center">
<tr>
<td role="modules-container" style="padding: 0px 0px 0px 0px; color: #626262; text-align: left;" bgcolor="#F4F4F4" width="100%"
align="left">
<table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0"
width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
<tr>
<td role="module-content">
<p>reviewer declined</p>
</td>
</tr>
</table>
<table class="wrapper" role="module" data-type="image" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;">
<tr>
<td style="font-size:6px;line-height:10px;padding:20px 0px 20px 0px;" valign="top" align="center">
<img class="max-width" border="0" style="display:block;color:#000000;text-decoration:none;font-family:Helvetica, arial, sans-serif;font-size:16px;max-width:10% !important;width:10%;height:auto !important;"
src="https://marketing-image-production.s3.amazonaws.com/uploads/bb39b20cf15e52c1c0933676e25f2b2402737c6560b8098c204ad6932b84eb2058804376dbc4db138c7a21dcaed9325bde36185648afac5bc97e3d73d4e12718.png"
alt="" width="60">
</td>
</tr>
</table>
<table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;">
<tr>
<td style="padding:30px 23px 0px 23px;background-color:#ffffff;" height="100%" valign="top" bgcolor="#ffffff">
<div>
<p data-pm-slice="1 1 []">Dear {{editorName}},</p>
<p>&nbsp;</p>
We regret to inform you that {{reviewerName}} has declined to review the manuscript titled &quot;{{ title }}&quot; by {{authorName}}</p>
<p>&nbsp;</p>
<p>Please visit the <a href="{{manuscriptUrl}}">manuscript details page</a> to see if you need to invite any additional reviewers in order to reach a decision on the manuscript.</p>
<p>&nbsp;</p>
<p>Best regards,
<br /> {{eicName}}
<br /> Hindawi
</p>
<p>&nbsp;</p>
</div>
</td>
</tr>
</table>
<div data-role="module-unsubscribe" class="module unsubscribe-css__unsubscribe___2CDlR" role="module" data-type="unsubscribe"
style="color:#444444;font-size:12px;line-height:20px;padding:16px 16px 16px 16px;text-align:center">
<div class="Unsubscribe--addressLine">
<p class="Unsubscribe--senderName" style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px">Hindawi Publishing Corporation</p>
<p style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px">
<span class="Unsubscribe--senderAddress">315 Madison Ave, Third Floor, Suite 3070</span>,
<span class="Unsubscribe--senderCity">NEW YORK</span>,
<span class="Unsubscribe--senderState">NY</span>
<span class="Unsubscribe--senderZip">10017</span>
</p>
</div>
<p style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px">
<a class="Unsubscribe--unsubscribeLink" href="[Unsubscribe]">Unsubscribe</a>
</p>
</div>
</td>
</tr>
</table>
<!--[if mso]>
</td></tr></table>
</center>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</center>
</body>
</html>
\ No newline at end of file
Dear {{ editorName }}
We regret to inform you that {{ reviewerName }} has declined to review the manuscript titled "{{ title }}" by {{authorName}}.
Please visit the manuscript details page to see if you need to invite any additional reviewers in order to reach a decision on the manuscript.
Best regards,
{{eicName}}
Hindawi
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