Skip to content
Snippets Groups Projects
Commit d8687f6f authored by Mihail Hagiu's avatar Mihail Hagiu
Browse files

fix(ReviewerNumbering): Merged with develop

parents 6f18bd26 9a0fa086
No related branches found
No related tags found
3 merge requests!176Sprint 24,!171Sprint 24,!158Hin 1115
Showing
with 462 additions and 162 deletions
...@@ -56,13 +56,15 @@ The `Email` class also provides a `constructor` whose properties will be used wh ...@@ -56,13 +56,15 @@ The `Email` class also provides a `constructor` whose properties will be used wh
![notification](https://gitlab.coko.foundation/xpub/xpub-faraday/uploads/27cb6acc8ff4a07758f55e5ea0504d28/notification.png) ![notification](https://gitlab.coko.foundation/xpub/xpub-faraday/uploads/27cb6acc8ff4a07758f55e5ea0504d28/notification.png)
```javascript ```javascript
const emailTemplate = require('@pubsweet/component-email-template') const EmailTemplate = require('@pubsweet/component-email-template')
const config = require('config') const config = require('config')
const { name: journalName, fromEmail: staffEmail } = config.get('journal') const { name: journalName, fromEmail: staffEmail } = config.get('journal')
const paragraph = `We are please to inform you that the manuscript has passed the technical check process and is now submitted. Please click the link below to access the manuscript.`
const sendNotifications = ({ user, editor, collection, fragment }) => { const sendNotifications = ({ user, editor, collection, fragment }) => {
const email = new emailTemplate({ const email = new EmailTemplate({
type: 'user', type: 'user',
fromEmail, fromEmail,
toUser: { toUser: {
...@@ -74,18 +76,19 @@ The `Email` class also provides a `constructor` whose properties will be used wh ...@@ -74,18 +76,19 @@ The `Email` class also provides a `constructor` whose properties will be used wh
signatureJournal: journalName, signatureJournal: journalName,
signatureName: `${editor.name}`, signatureName: `${editor.name}`,
subject: `${collection.customId}: Manuscript Update`, subject: `${collection.customId}: Manuscript Update`,
paragraph,
unsubscribeLink: `http://localhost:3000/unsubscribe/${user.id}`, unsubscribeLink: `http://localhost:3000/unsubscribe/${user.id}`,
ctaLink: `http://localhost:3000/projects/${collection.id}/versions/${ ctaLink: `http://localhost:3000/projects/${collection.id}/versions/${
fragment.id fragment.id
}/details`, }/details`,
}, },
bodyProps: {
hasLink: true,
hasIntro: true,
hasSignature: true }
}) })
const paragraph = `We are please to inform you that the manuscript has passed the technical check process and is now submitted. Please click the link below to access the manuscript.` return email.sendEmail()
const { html, text } = email.getNotificationBody({ emailBodyProps: { paragraph, hasLink: true, hasIntro: true, hasSignature: true }})
email.sendEmail({ html, text })
} }
``` ```
......
const config = require('config') const config = require('config')
const helpers = require('./helpers') const htmlTemplateService = require('./HTMLTemplateService')
const SendEmail = require('@pubsweet/component-send-email') const SendEmail = require('@pubsweet/component-send-email')
const logger = require('@pubsweet/logger')
const configData = { const configData = {
logo: config.get('journal.logo'), logo: config.get('journal.logo'),
...@@ -10,9 +11,10 @@ const configData = { ...@@ -10,9 +11,10 @@ const configData = {
logoLink: config.get('journal.logoLink'), logoLink: config.get('journal.logoLink'),
publisher: config.get('journal.publisher'), publisher: config.get('journal.publisher'),
} }
class Email { class EmailTemplate {
constructor({ constructor({
type = 'system', type = 'system',
templateType = 'notification',
fromEmail = config.get('journal.staffEmail'), fromEmail = config.get('journal.staffEmail'),
toUser = { toUser = {
id: '', id: '',
...@@ -23,15 +25,19 @@ class Email { ...@@ -23,15 +25,19 @@ class Email {
subject: '', subject: '',
ctaLink: '', ctaLink: '',
ctaText: '', ctaText: '',
paragraph: '',
signatureName: '', signatureName: '',
unsubscribeLink: '', unsubscribeLink: '',
signatureJournal: '', signatureJournal: '',
}, },
bodyProps = { hasLink: false, hasIntro: false, hasSignature: false },
}) { }) {
this.type = type this.type = type
this.toUser = toUser this.toUser = toUser
this.content = content this.content = content
this.bodyProps = bodyProps
this.fromEmail = fromEmail this.fromEmail = fromEmail
this.templateType = templateType
} }
set _toUser(newToUser) { set _toUser(newToUser) {
...@@ -46,41 +52,49 @@ class Email { ...@@ -46,41 +52,49 @@ class Email {
this.content = newContent this.content = newContent
} }
getInvitationBody({ emailBodyProps }) { _getInvitationBody() {
return { return {
html: helpers.getCompiledInvitationBody({ html: htmlTemplateService.getCompiledInvitationBody({
replacements: { replacements: {
...configData, ...configData,
...this.content, ...this.content,
...emailBodyProps, ...this.bodyProps,
toEmail: this.toUser.email, toEmail: this.toUser.email,
toUserName: this.toUser.name, toUserName: this.toUser.name,
}, },
}), }),
text: `${emailBodyProps.resend} ${emailBodyProps.upperContent} ${ text: `${this.bodyProps.resend} ${this.bodyProps.upperContent} ${
emailBodyProps.manuscriptText this.bodyProps.manuscriptText
} ${emailBodyProps.lowerContent} ${this.content.signatureName}`, } ${this.bodyProps.lowerContent} ${this.content.signatureName}`,
} }
} }
getNotificationBody({ emailBodyProps }) { _getNotificationBody() {
return { return {
html: helpers.getCompiledNotificationBody({ html: htmlTemplateService.getCompiledNotificationBody({
replacements: { replacements: {
...configData, ...configData,
...this.content, ...this.content,
...emailBodyProps, ...this.bodyProps,
toEmail: this.toUser.email, toEmail: this.toUser.email,
toUserName: this.toUser.name, toUserName: this.toUser.name,
}, },
}), }),
text: `${emailBodyProps.paragraph} ${this.content.ctaLink} ${ text: `${this.content.paragraph} ${this.content.ctaLink} ${
this.content.ctaText this.content.ctaText
} ${this.content.signatureName}`, } ${this.content.signatureName}`,
} }
} }
async sendEmail({ text, html }) { _getEmailTemplate() {
return this.templateType === 'notification'
? this._getNotificationBody()
: this._getInvitationBody()
}
async sendEmail() {
const { html, text } = this._getEmailTemplate()
const { fromEmail: from } = this const { fromEmail: from } = this
const { email: to } = this.toUser const { email: to } = this.toUser
const { subject } = this.content const { subject } = this.content
...@@ -95,10 +109,17 @@ class Email { ...@@ -95,10 +109,17 @@ class Email {
try { try {
await SendEmail.send(mailData) await SendEmail.send(mailData)
logger.info(
`Sent email from: ${from} to: ${to} with subject: "${subject}"`,
)
logger.debug(
`Sent email from: ${from} to: ${to} with subject: "${subject}"`,
)
} catch (e) { } catch (e) {
logger.error(e)
throw new Error(e) throw new Error(e)
} }
} }
} }
module.exports = Email module.exports = EmailTemplate
...@@ -24,14 +24,7 @@ const getCompiledInvitationBody = ({ replacements }) => { ...@@ -24,14 +24,7 @@ const getCompiledInvitationBody = ({ replacements }) => {
return compileBody({ fileName: 'invitation', context: replacements }) return compileBody({ fileName: 'invitation', context: replacements })
} }
const readFile = path => const readFile = path => fs.readFileSync(path, 'utf-8')
fs.readFileSync(path, { encoding: 'utf-8' }, (err, file) => {
if (err) {
throw err
} else {
return file
}
})
const handlePartial = (partialName, context = {}) => { const handlePartial = (partialName, context = {}) => {
let partial = readFile(`${__dirname}/templates/partials/${partialName}.hbs`) let partial = readFile(`${__dirname}/templates/partials/${partialName}.hbs`)
......
...@@ -2,7 +2,7 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' ...@@ -2,7 +2,7 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
process.env.SUPPRESS_NO_CONFIG_WARNING = true process.env.SUPPRESS_NO_CONFIG_WARNING = true
const { cloneDeep } = require('lodash') const { cloneDeep } = require('lodash')
const helpers = require('../src/helpers') const htmlTemplateService = require('../src/HTMLTemplateService')
const emailProps = { const emailProps = {
toUserName: 'Peter Griffin', toUserName: 'Peter Griffin',
...@@ -19,7 +19,7 @@ describe('Email template helpers', () => { ...@@ -19,7 +19,7 @@ describe('Email template helpers', () => {
replacements = cloneDeep(emailProps) replacements = cloneDeep(emailProps)
}) })
it('should return the notification HTML with CTA', () => { it('should return the notification HTML with CTA', () => {
const notificationBody = helpers.getCompiledNotificationBody({ const notificationBody = htmlTemplateService.getCompiledNotificationBody({
replacements, replacements,
}) })
...@@ -32,7 +32,7 @@ describe('Email template helpers', () => { ...@@ -32,7 +32,7 @@ describe('Email template helpers', () => {
it('should return the notification HTML without CTA', () => { it('should return the notification HTML without CTA', () => {
replacements.hasLink = false replacements.hasLink = false
const notificationBody = helpers.getCompiledNotificationBody({ const notificationBody = htmlTemplateService.getCompiledNotificationBody({
replacements, replacements,
}) })
expect(notificationBody).toContain('Peter Griffin') expect(notificationBody).toContain('Peter Griffin')
...@@ -41,7 +41,7 @@ describe('Email template helpers', () => { ...@@ -41,7 +41,7 @@ describe('Email template helpers', () => {
}) })
it('should return the notification HTML without intro', () => { it('should return the notification HTML without intro', () => {
replacements.hasIntro = false replacements.hasIntro = false
const notificationBody = helpers.getCompiledNotificationBody({ const notificationBody = htmlTemplateService.getCompiledNotificationBody({
replacements, replacements,
}) })
expect(notificationBody).not.toContain('Peter Griffin') expect(notificationBody).not.toContain('Peter Griffin')
...@@ -50,7 +50,7 @@ describe('Email template helpers', () => { ...@@ -50,7 +50,7 @@ describe('Email template helpers', () => {
}) })
it('should return the notification HTML without signature', () => { it('should return the notification HTML without signature', () => {
replacements.hasSignature = false replacements.hasSignature = false
const notificationBody = helpers.getCompiledNotificationBody({ const notificationBody = htmlTemplateService.getCompiledNotificationBody({
replacements, replacements,
}) })
expect(notificationBody).toContain('Peter Griffin') expect(notificationBody).toContain('Peter Griffin')
......
...@@ -3,8 +3,6 @@ const config = require('config') ...@@ -3,8 +3,6 @@ const config = require('config')
const journalName = config.get('journal.name') const journalName = config.get('journal.name')
const getEmailCopy = ({ emailType, role }) => { const getEmailCopy = ({ emailType, role }) => {
let paragraph let paragraph
const hasIntro = true
const hasSignature = true
switch (emailType) { switch (emailType) {
case 'user-signup': case 'user-signup':
paragraph = `Thank you for creating an account on Hindawi’s review system. paragraph = `Thank you for creating an account on Hindawi’s review system.
...@@ -23,7 +21,7 @@ const getEmailCopy = ({ emailType, role }) => { ...@@ -23,7 +21,7 @@ const getEmailCopy = ({ emailType, role }) => {
throw new Error(`The ${emailType} email type is not defined.`) throw new Error(`The ${emailType} email type is not defined.`)
} }
return { paragraph, hasLink: true, hasIntro, hasSignature } return { paragraph, hasLink: true, hasIntro: true, hasSignature: true }
} }
module.exports = { module.exports = {
......
const config = require('config')
const { services } = require('pubsweet-component-helper-service')
const { getEmailCopy } = require('./emailCopy')
const confirmSignUp = config.get('confirm-signup.url')
module.exports = {
sendNewUserEmail: ({ email, role }) => {
email.content.subject = 'Confirm your account'
const emailType =
role === 'Handling Editor' ? 'he-added-by-admin' : 'user-added-by-admin'
const { html, text } = email.getNotificationBody({
emailBodyProps: getEmailCopy({
role,
emailType,
}),
})
email.sendEmail({ html, text })
},
sendSignupEmail: ({ email, baseUrl, user }) => {
email.content.subject = 'Confirm your email address'
email.content.ctaLink = services.createUrl(baseUrl, confirmSignUp, {
userId: user.id,
confirmationToken: user.accessTokens.confirmation,
})
const { html, text } = email.getNotificationBody({
emailBodyProps: getEmailCopy({
emailType: 'user-signup',
}),
})
email.sendEmail({ html, text })
},
}
const config = require('config') const config = require('config')
const unsubscribeSlug = config.get('unsubscribe.url') const unsubscribeSlug = config.get('unsubscribe.url')
const resetPath = config.get('invite-reset-password.url')
const { staffEmail, name: journalName } = config.get('journal') const { staffEmail, name: journalName } = config.get('journal')
const Email = require('@pubsweet/component-email-templating') const Email = require('@pubsweet/component-email-templating')
const { services } = require('pubsweet-component-helper-service') const { services } = require('pubsweet-component-helper-service')
const { getEmailCopy } = require('./emailCopy')
const { sendNewUserEmail, sendSignupEmail } = require('./helpers')
module.exports = { module.exports = {
async sendNotifications({ user, baseUrl, role }) { sendNewUserEmail: ({ user, baseUrl, role }) => {
const resetPath = config.get('invite-reset-password.url')
const emailType =
role === 'Handling Editor' ? 'he-added-by-admin' : 'user-added-by-admin'
const { paragraph, ...bodyProps } = getEmailCopy({
role,
emailType,
})
const email = new Email({ const email = new Email({
type: 'user', type: 'user',
fromEmail: `${journalName} <${staffEmail}>`, fromEmail: `${journalName} <${staffEmail}>`,
...@@ -18,6 +26,7 @@ module.exports = { ...@@ -18,6 +26,7 @@ module.exports = {
name: `${user.lastName}`, name: `${user.lastName}`,
}, },
content: { content: {
subject: 'Confirm your account',
ctaLink: services.createUrl(baseUrl, resetPath, { ctaLink: services.createUrl(baseUrl, resetPath, {
email: user.email, email: user.email,
token: user.accessTokens.passwordReset, token: user.accessTokens.passwordReset,
...@@ -28,18 +37,49 @@ module.exports = { ...@@ -28,18 +37,49 @@ module.exports = {
country: user.country, country: user.country,
}), }),
ctaText: 'CONFIRM ACCOUNT', ctaText: 'CONFIRM ACCOUNT',
paragraph,
signatureName: 'Hindawi',
unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
id: user.id,
token: user.accessTokens.unsubscribe,
}),
},
bodyProps,
})
return email.sendEmail()
},
sendSignupEmail: ({ user, baseUrl }) => {
const confirmSignUp = config.get('confirm-signup.url')
const { paragraph, ...bodyProps } = getEmailCopy({
emailType: 'user-signup',
})
const email = new Email({
type: 'user',
fromEmail: `${journalName} <${staffEmail}>`,
toUser: {
email: user.email,
name: `${user.lastName}`,
},
content: {
subject: 'Confirm your email address',
ctaLink: services.createUrl(baseUrl, confirmSignUp, {
userId: user.id,
confirmationToken: user.accessTokens.confirmation,
}),
ctaText: 'CONFIRM ACCOUNT',
paragraph,
signatureName: 'Hindawi', signatureName: 'Hindawi',
unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, { unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
id: user.id, id: user.id,
token: user.accessTokens.unsubscribe, token: user.accessTokens.unsubscribe,
}), }),
}, },
bodyProps,
}) })
if (role) { return email.sendEmail()
sendNewUserEmail({ email, role })
} else {
sendSignupEmail({ email, baseUrl, user })
}
}, },
} }
...@@ -16,53 +16,49 @@ module.exports = models => async (req, res) => { ...@@ -16,53 +16,49 @@ module.exports = models => async (req, res) => {
return res.status(400).json({ error: `Email type ${type} is not defined.` }) return res.status(400).json({ error: `Email type ${type} is not defined.` })
} }
const UserModel = models.User let user
try { try {
const user = await UserModel.findByEmail(email) user = await models.User.findByEmail(email)
} catch (e) {
const notFoundError = await services.handleNotFoundError(e, 'User')
return res.status(notFoundError.status).json({
error: notFoundError.message,
})
}
if (type === 'signup') { if (type === 'signup') {
if (!user.accessTokens.confirmation) { if (!user.accessTokens.confirmation) {
return res return res
.status(400) .status(400)
.json({ error: 'User does not have a confirmation token.' }) .json({ error: 'User does not have a confirmation token.' })
}
} }
notifications.sendSignupEmail({ user, baseUrl: services.getBaseUrl(req) })
} else if (type === 'invite') {
let emailRole let emailRole
if (type === 'invite') { switch (role) {
switch (role) { case 'handlingEditor':
case 'handlingEditor': emailRole = 'Handling Editor'
emailRole = 'Handling Editor' break
break case 'editorInChief':
case 'editorInChief': emailRole = 'Editor in Chief'
emailRole = 'Editor in Chief' break
break case 'admin':
case 'admin': emailRole = 'Administrator'
emailRole = 'Administrator' break
break case 'author':
case 'author': emailRole = 'Author'
emailRole = 'Author' break
break default:
default: return res.status(400).json({
return res.status(400).json({ error: `Role ${role} is not defined.`,
error: `Role ${role} is not defined.`, })
})
}
} }
notifications.sendNewUserEmail({
notifications.sendNotifications({
user, user,
role: emailRole,
UserModel: models.User,
baseUrl: services.getBaseUrl(req), baseUrl: services.getBaseUrl(req),
}) role: emailRole,
return res.status(200).json({})
} catch (e) {
const notFoundError = await services.handleNotFoundError(e, 'User')
return res.status(notFoundError.status).json({
error: notFoundError.message,
}) })
} }
return res.status(200).json({})
} }
import React from 'react' import React, { Fragment } from 'react'
import { get } from 'lodash' import { get } from 'lodash'
import { withProps } from 'recompose' import { withProps } from 'recompose'
import styled from 'styled-components' import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit' import { th } from '@pubsweet/ui-toolkit'
import { DateParser } from '@pubsweet/ui' import { DateParser } from '@pubsweet/ui'
import { Label, Item, Row, Text } from './' import { Label, Item, Row, Text, FileItem } from './'
const submittingAuthor = authors => { const submittingAuthor = authors => {
const thisAuthor = authors.filter(e => e.isSubmitting) const thisAuthor = authors.filter(e => e.isSubmitting)
return thisAuthor[0] return thisAuthor[0]
} }
const AuthorReply = ({ reply, authorName, submittedOn }) => ( const AuthorReply = ({
authorName,
submittedOn,
replyContent,
replyFile,
onDownload,
onPreview,
}) => (
<Root> <Root>
<Row justify="space-between" mb={2}> <Row justify="space-between" mb={2}>
<Item justify="flex-end"> <Item justify="flex-end">
<Row mb={1}> <Row mb={1}>
<Item vertical> <Item vertical>
<Label mb={1 / 2}>Author Reply</Label> <Label mb={1 / 2}>Author Reply</Label>
<Text>{reply}</Text> <Text>{replyContent}</Text>
</Item> </Item>
</Row> </Row>
<Text ml={1} mr={1} whiteSpace="nowrap"> <Text ml={1} mr={1} whiteSpace="nowrap">
...@@ -30,17 +37,36 @@ const AuthorReply = ({ reply, authorName, submittedOn }) => ( ...@@ -30,17 +37,36 @@ const AuthorReply = ({ reply, authorName, submittedOn }) => (
</DateParser> </DateParser>
</Item> </Item>
</Row> </Row>
{replyFile && (
<Fragment>
<Label mb={1 / 2}>File</Label>
<Row justify="flex-start" mb={2}>
<Item flex={0} mr={1}>
<FileItem
item={replyFile}
onDownload={onDownload}
onPreview={onPreview}
/>
</Item>
</Row>
</Fragment>
)}
</Root> </Root>
) )
export default withProps(({ fragment: { authors, submitted } }) => ({ export default withProps(
submittedOn: submitted, ({ fragment: { authors, submitted }, authorReply }) => ({
authorName: `${get(submittingAuthor(authors), 'firstName', '')} ${get( submittedOn: submitted,
submittingAuthor(authors), authorName: `${get(submittingAuthor(authors), 'firstName', '')} ${get(
'lastName', submittingAuthor(authors),
'', 'lastName',
)}`, '',
}))(AuthorReply) )}`,
replyContent: get(authorReply, 'content', ''),
replyFile: get(authorReply, 'file', ''),
}),
)(AuthorReply)
// #region styles // #region styles
const Root = styled.div` const Root = styled.div`
......
...@@ -76,7 +76,7 @@ const ManuscriptCard = ({ ...@@ -76,7 +76,7 @@ const ManuscriptCard = ({
<Row alignItems="center" justify="flex-start" mb={1}> <Row alignItems="center" justify="flex-start" mb={1}>
<H4>Handling editor</H4> <H4>Handling editor</H4>
<Text ml={1} mr={3} whiteSpace="nowrap"> <Text ml={1} mr={3} whiteSpace="nowrap">
{get(handlingEditor, 'name', 'Unassigned')} {get(handlingEditor, 'name', 'Undefined')}
</Text> </Text>
{canViewReports && ( {canViewReports && (
<Fragment> <Fragment>
......
import React from 'react' import React from 'react'
import { compose } from 'recompose'
import { ContextualBox, AuthorReply } from '../' import {
ContextualBox,
AuthorReply,
withFilePreview,
withFileDownload,
} from '../'
const ResponseToRevisionRequest = ({ const ResponseToRevisionRequest = ({
fragment, fragment,
authorReply, authorReply,
toggle, toggle,
expanded, expanded,
downloadFile,
previewFile,
...rest
}) => ( }) => (
<ContextualBox <ContextualBox
expanded={expanded} expanded={expanded}
label="Response to Revision Request" label="Response to Revision Request"
mb={2} mb={2}
toggle={toggle} toggle={toggle}
{...rest}
> >
<AuthorReply fragment={fragment} reply={authorReply} /> <AuthorReply
authorReply={authorReply}
fragment={fragment}
onDownload={downloadFile}
onPreview={previewFile}
/>
</ContextualBox> </ContextualBox>
) )
export default ResponseToRevisionRequest export default compose(withFilePreview, withFileDownload)(
ResponseToRevisionRequest,
)
...@@ -170,7 +170,7 @@ export default compose( ...@@ -170,7 +170,7 @@ export default compose(
</Button> </Button>
) )
} }
return <Text ml={1}>Unassigned</Text> return <Text ml={1}>Assigned</Text>
}, },
}), }),
setDisplayName('ManuscriptHeader'), setDisplayName('ManuscriptHeader'),
......
...@@ -8,4 +8,5 @@ module.exports = { ...@@ -8,4 +8,5 @@ module.exports = {
collectionNoInvitesID: chance.guid(), collectionNoInvitesID: chance.guid(),
twoVersionsCollectionId: chance.guid(), twoVersionsCollectionId: chance.guid(),
oneReviewedFragmentCollectionID: chance.guid(), oneReviewedFragmentCollectionID: chance.guid(),
noEditorRecomedationCollectionID: chance.guid(),
} }
const Chance = require('chance') const Chance = require('chance')
const { user, handlingEditor, answerHE } = require('./userData') const {
user,
handlingEditor,
answerHE,
noRecommendationHE,
} = require('./userData')
const { const {
fragment, fragment,
fragment1, fragment1,
reviewCompletedFragment, reviewCompletedFragment,
noInvitesFragment, noInvitesFragment,
noEditorRecomedationFragment,
} = require('./fragments') } = require('./fragments')
const { const {
standardCollID, standardCollID,
...@@ -12,6 +18,7 @@ const { ...@@ -12,6 +18,7 @@ const {
collectionNoInvitesID, collectionNoInvitesID,
twoVersionsCollectionId, twoVersionsCollectionId,
oneReviewedFragmentCollectionID, oneReviewedFragmentCollectionID,
noEditorRecomedationCollectionID,
} = require('./collectionIDs') } = require('./collectionIDs')
const chance = new Chance() const chance = new Chance()
...@@ -215,6 +222,48 @@ const collections = { ...@@ -215,6 +222,48 @@ const collections = {
save: jest.fn(() => collections.collection), save: jest.fn(() => collections.collection),
customId: chance.natural({ min: 999999, max: 9999999 }), customId: chance.natural({ min: 999999, max: 9999999 }),
}, },
noEditorRecomedationCollection: {
id: noEditorRecomedationCollectionID,
title: chance.sentence(),
type: 'collection',
fragments: [noEditorRecomedationFragment.id],
owners: [user.id],
save: jest.fn(() => collections.noEditorRecomedationCollection),
invitations: [
{
id: chance.guid(),
role: 'handlingEditor',
hasAnswer: true,
isAccepted: true,
userId: noRecommendationHE.id,
invitedOn: chance.timestamp(),
respondedOn: null,
},
{
id: chance.guid(),
role: 'handlingEditor',
hasAnswer: false,
isAccepted: false,
userId: answerHE.id,
invitedOn: chance.timestamp(),
respondedOn: chance.timestamp(),
},
],
handlingEditor: {
id: handlingEditor.id,
hasAnswer: true,
isAccepted: true,
email: handlingEditor.email,
invitedOn: chance.timestamp(),
respondedOn: chance.timestamp(),
name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
},
customId: chance.natural({ min: 999999, max: 9999999 }),
technicalChecks: {
token: chance.guid(),
},
status: 'reviewCompleted',
},
oneReviewedFragmentCollection: { oneReviewedFragmentCollection: {
id: oneReviewedFragmentCollectionID, id: oneReviewedFragmentCollectionID,
title: chance.sentence(), title: chance.sentence(),
......
...@@ -8,11 +8,13 @@ const { ...@@ -8,11 +8,13 @@ const {
admin, admin,
inactiveReviewer, inactiveReviewer,
reviewer1, reviewer1,
newReviewer,
} = require('./userData') } = require('./userData')
const { const {
standardCollID, standardCollID,
collectionReviewCompletedID, collectionReviewCompletedID,
collectionNoInvitesID, collectionNoInvitesID,
noEditorRecomedationCollectionID,
} = require('./collectionIDs') } = require('./collectionIDs')
const { user } = require('./userData') const { user } = require('./userData')
...@@ -71,7 +73,7 @@ const fragments = { ...@@ -71,7 +73,7 @@ const fragments = {
}, },
], ],
id: chance.guid(), id: chance.guid(),
userId: '1231njfsdknfkjs23', userId: recReviewer.id,
createdOn: chance.timestamp(), createdOn: chance.timestamp(),
updatedOn: chance.timestamp(), updatedOn: chance.timestamp(),
submittedOn: chance.timestamp(), submittedOn: chance.timestamp(),
...@@ -148,7 +150,7 @@ const fragments = { ...@@ -148,7 +150,7 @@ const fragments = {
id: chance.guid(), id: chance.guid(),
role: 'reviewer', role: 'reviewer',
hasAnswer: true, hasAnswer: true,
isAccepted: false, isAccepted: true,
userId: answerReviewer.id, userId: answerReviewer.id,
invitedOn: chance.timestamp(), invitedOn: chance.timestamp(),
respondedOn: chance.timestamp(), respondedOn: chance.timestamp(),
...@@ -164,6 +166,16 @@ const fragments = { ...@@ -164,6 +166,16 @@ const fragments = {
respondedOn: chance.timestamp(), respondedOn: chance.timestamp(),
type: 'invitation', type: 'invitation',
}, },
{
id: chance.guid(),
role: 'reviewer',
hasAnswer: true,
isAccepted: true,
userId: recReviewer.id,
invitedOn: chance.timestamp(),
respondedOn: chance.timestamp(),
type: 'invitation',
},
], ],
save: jest.fn(() => fragments.fragment), save: jest.fn(() => fragments.fragment),
owners: [user.id], owners: [user.id],
...@@ -418,6 +430,61 @@ const fragments = { ...@@ -418,6 +430,61 @@ const fragments = {
type: 'fragment', type: 'fragment',
}, },
}, },
noEditorRecomedationFragment: {
id: chance.guid(),
collectionId: noEditorRecomedationCollectionID,
metadata: {
title: chance.sentence(),
abstract: chance.paragraph(),
},
recommendations: [
{
recommendation: 'publish',
recommendationType: 'review',
comments: [
{
content: chance.paragraph(),
public: chance.bool(),
files: [
{
id: chance.guid(),
name: 'file.pdf',
size: chance.natural(),
},
],
},
],
id: chance.guid(),
userId: newReviewer.id,
createdOn: chance.timestamp(),
updatedOn: chance.timestamp(),
submittedOn: chance.timestamp(),
},
],
authors: [
{
email: chance.email(),
id: submittingAuthor.id,
isSubmitting: true,
isCorresponding: false,
},
],
invitations: [
{
id: chance.guid(),
role: 'reviewer',
hasAnswer: true,
isAccepted: true,
userId: newReviewer.id,
invitedOn: chance.timestamp(),
respondedOn: chance.timestamp(),
type: 'invitation',
},
],
save: jest.fn(() => fragments.fragment),
owners: [user.id],
type: 'fragment',
},
} }
fragments.noInvitesFragment = { fragments.noInvitesFragment = {
......
...@@ -8,4 +8,6 @@ module.exports = { ...@@ -8,4 +8,6 @@ module.exports = {
authorTeamID: chance.guid(), authorTeamID: chance.guid(),
revRecommendationTeamID: chance.guid(), revRecommendationTeamID: chance.guid(),
rev1TeamID: chance.guid(), rev1TeamID: chance.guid(),
heNoRecommendationTeamID: chance.guid(),
revNoEditorRecommendationTeamID: chance.guid(),
} }
...@@ -8,11 +8,18 @@ const { ...@@ -8,11 +8,18 @@ const {
authorTeamID, authorTeamID,
revRecommendationTeamID, revRecommendationTeamID,
rev1TeamID, rev1TeamID,
heNoRecommendationTeamID,
revNoEditorRecommendationTeamID,
} = require('./teamIDs') } = require('./teamIDs')
const { submittingAuthor } = require('./userData') const { submittingAuthor } = require('./userData')
const { collection } = collections const { collection, noEditorRecomedationCollection } = collections
const { fragment, reviewCompletedFragment, fragment1 } = fragments const {
fragment,
reviewCompletedFragment,
fragment1,
noEditorRecomedationFragment,
} = fragments
const { const {
handlingEditor, handlingEditor,
reviewer, reviewer,
...@@ -20,6 +27,8 @@ const { ...@@ -20,6 +27,8 @@ const {
answerReviewer, answerReviewer,
recReviewer, recReviewer,
reviewer1, reviewer1,
newReviewer,
noRecommendationHE,
} = users } = users
const teams = { const teams = {
heTeam: { heTeam: {
...@@ -39,6 +48,23 @@ const teams = { ...@@ -39,6 +48,23 @@ const teams = {
updateProperties: jest.fn(() => teams.heTeam), updateProperties: jest.fn(() => teams.heTeam),
id: heTeamID, id: heTeamID,
}, },
heNoRecommendationTeam: {
teamType: {
name: 'handlingEditor',
permissions: 'handlingEditor',
},
group: 'handlingEditor',
name: 'HandlingEditor',
object: {
type: 'collection',
id: noEditorRecomedationCollection.id,
},
members: [noRecommendationHE.id],
save: jest.fn(() => teams.heNoRecommendationTeam),
delete: jest.fn(),
updateProperties: jest.fn(() => teams.heNoRecommendationTeam),
id: heNoRecommendationTeamID,
},
revTeam: { revTeam: {
teamType: { teamType: {
name: 'reviewer', name: 'reviewer',
...@@ -106,5 +132,21 @@ const teams = { ...@@ -106,5 +132,21 @@ const teams = {
updateProperties: jest.fn(() => teams.authorTeam), updateProperties: jest.fn(() => teams.authorTeam),
id: authorTeamID, id: authorTeamID,
}, },
revNoEditorRecommendationTeam: {
teamType: {
name: 'reviewer',
permissions: 'reviewer',
},
group: 'reviewer',
name: 'reviewer',
object: {
type: 'fragment',
id: noEditorRecomedationFragment.id,
},
members: [newReviewer.id],
save: jest.fn(() => teams.revNoEditorRecommendationTeam),
updateProperties: jest.fn(() => teams.revNoEditorRecommendationTeam),
id: revNoEditorRecommendationTeamID,
},
} }
module.exports = teams module.exports = teams
...@@ -10,11 +10,13 @@ const generateUserData = () => ({ ...@@ -10,11 +10,13 @@ const generateUserData = () => ({
module.exports = { module.exports = {
handlingEditor: generateUserData(), handlingEditor: generateUserData(),
noRecommendationHE: generateUserData(),
user: generateUserData(), user: generateUserData(),
admin: generateUserData(), admin: generateUserData(),
author: generateUserData(), author: generateUserData(),
reviewer: generateUserData(), reviewer: generateUserData(),
reviewer1: generateUserData(), reviewer1: generateUserData(),
newReviewer: generateUserData(),
answerReviewer: generateUserData(), answerReviewer: generateUserData(),
submittingAuthor: generateUserData(), submittingAuthor: generateUserData(),
recReviewer: generateUserData(), recReviewer: generateUserData(),
......
...@@ -8,6 +8,8 @@ const { ...@@ -8,6 +8,8 @@ const {
authorTeamID, authorTeamID,
revRecommendationTeamID, revRecommendationTeamID,
rev1TeamID, rev1TeamID,
heNoRecommendationTeamID,
revNoEditorRecommendationTeamID,
} = require('./teamIDs') } = require('./teamIDs')
const keys = Object.keys(usersData) const keys = Object.keys(usersData)
...@@ -17,21 +19,37 @@ users = keys.reduce((obj, item) => { ...@@ -17,21 +19,37 @@ users = keys.reduce((obj, item) => {
const isHE = item === 'answerHE' || item === 'handlingEditor' const isHE = item === 'answerHE' || item === 'handlingEditor'
let teams = [] let teams = []
if (isHE) { switch (item) {
teams = [heTeamID] case 'answerHE':
} teams = [heTeamID]
if (item === 'author') { break
teams = [authorTeamID] case 'handlingEditor':
} teams = [heTeamID]
if (['reviewer', 'inactiveReviewer', 'answerReviewer'].includes(item)) { break
teams.push(revTeamID) case 'noRecommendationHE':
} teams = [heNoRecommendationTeamID]
break
if (['reviewer', 'answerReviewer', 'recReviewer'].includes(item)) { case 'author':
teams.push(revRecommendationTeamID) teams = [authorTeamID]
} break
if (item === 'reviewer1') { case 'reviewer1':
teams = [rev1TeamID] teams = [rev1TeamID]
break
case 'newReviewer':
teams = [revNoEditorRecommendationTeamID]
break
case 'reviewer':
case 'answerReviewer':
teams = [revTeamID, revRecommendationTeamID]
break
case 'inactiveReviewer':
teams.push(revTeamID)
break
case 'recReviewer':
teams.push(revRecommendationTeamID)
break
default:
teams = []
} }
obj[item] = { obj[item] = {
......
const { get, remove } = require('lodash') const { get, remove } = require('lodash')
const config = require('config')
const User = require('./User') const User = require('./User')
const { recommendations: configRecommendations } = config
class Fragment { class Fragment {
constructor({ fragment }) { constructor({ fragment }) {
this.fragment = fragment this.fragment = fragment
...@@ -146,6 +149,67 @@ class Fragment { ...@@ -146,6 +149,67 @@ class Fragment {
rec => rec.recommendationType === 'review' && rec.submittedOn, rec => rec.recommendationType === 'review' && rec.submittedOn,
) )
} }
async getReviewersAndEditorsData({ collection, UserModel }) {
const {
invitations = [],
recommendations = [],
submitted = Date.now(),
} = this.fragment
const revAndEditorData = await Promise.all(
recommendations
.filter(
rec =>
rec.recommendationType === configRecommendations.type.editor ||
(rec.recommendationType === configRecommendations.type.review &&
rec.submittedOn),
)
.map(async rec => {
const user = await UserModel.find(rec.userId)
let assignmentDate, isReviewer
if (rec.recommendationType === configRecommendations.type.editor) {
if (!collection.handlingEditor) {
throw new Error(
`Collection ${collection.id} does not have a Handling Editor`,
)
}
if (user.id === collection.handlingEditor.id) {
const editorInvitation = collection.invitations.find(
inv => inv.userId === user.id,
)
assignmentDate = editorInvitation.respondedOn
} else {
assignmentDate = submitted
}
isReviewer = false
} else {
const reviewerInvitation = invitations.find(
inv => inv.userId === user.id,
)
assignmentDate = reviewerInvitation.respondedOn
isReviewer = true
}
return {
isReviewer,
assignmentDate,
email: user.email,
title: user.title,
recommendation: rec,
country: user.country,
lastName: user.lastName,
firstName: user.firstName,
affiliation: user.affiliation,
submissionDate: rec.createdOn,
}
}),
)
return revAndEditorData
}
} }
module.exports = Fragment module.exports = Fragment
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