diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 868a670bd4a2db39005389eb4a5319fd7d06e828..1493dcaf07f232dc17d2ec03e4ea9e9090719b44 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -96,6 +96,7 @@ const authorAndReviewersCanViewReportsDetailsStatuses = [ 'rejected', 'accepted', 'reviewCompleted', + 'reviewersInvited', 'inQa', ] @@ -166,6 +167,7 @@ const canReviewerViewEditorialCommentsStatuses = [ 'reviewCompleted', 'pendingApproval', 'revisionRequested', + 'reviewersInvited', ] export const canReviewerViewEditorialComments = ( state, @@ -554,11 +556,12 @@ export const getVersionOptions = (state, collection = {}) => { export const canReview = (state, collection = {}, fragment = {}) => { const fragmentId = get(fragment, 'id', false) - if (!fragmentId) return false - + const ownRecommendation = getOwnRecommendations(state, fragmentId) const isReviewer = currentUserIsReviewer(state, fragmentId) if (!isReviewer) return false - - return get(collection, 'status', 'draft') === 'underReview' + return ( + get(collection, 'status', 'draft') === 'underReview' && + ownRecommendation.length === 0 + ) } diff --git a/packages/component-faraday-ui/src/ManuscriptCard.js b/packages/component-faraday-ui/src/ManuscriptCard.js index 81855c35b5db07bdacf105031d95f98b73e1ab14..c745c92345cfe359ef26ba1e0f335c050b5cd948 100644 --- a/packages/component-faraday-ui/src/ManuscriptCard.js +++ b/packages/component-faraday-ui/src/ManuscriptCard.js @@ -76,7 +76,7 @@ const ManuscriptCard = ({ <Row alignItems="center" justify="flex-start" mb={1}> <H4>Handling editor</H4> <Text ml={1} mr={3} whiteSpace="nowrap"> - {get(handlingEditor, 'name', 'Undefined')} + {get(handlingEditor, 'name', 'Unassigned')} </Text> {canViewReports && ( <Fragment> diff --git a/packages/component-faraday-ui/src/PersonInvitation.js b/packages/component-faraday-ui/src/PersonInvitation.js index 25e281528a87656906bd372c9af299c5c19e9416..9767fbee47fe992fd1db42ac708254603e5840bd 100644 --- a/packages/component-faraday-ui/src/PersonInvitation.js +++ b/packages/component-faraday-ui/src/PersonInvitation.js @@ -60,7 +60,7 @@ const PersonInvitation = ({ isFetching={isFetching} modalKey={`remove-${id}`} onConfirm={revokeInvitation} - subtitle={`Are you sure you want to remove ${email}? This decision will erase all data from the current fragment.`} + subtitle="Deleting the handling editor at this moment will also remove all his work." title="Revoke invitation?" > {showModal => ( diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js index 9c6f5b851a29553f89aae6ee59db2647f1ee58f6..296bebafcfc99f9f765f677a90d97d74f630b3e1 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js @@ -1,5 +1,5 @@ import React, { Fragment } from 'react' -import { get, chain } from 'lodash' +import { get, chain, isEmpty } from 'lodash' import { H2, H4, DateParser, Button } from '@pubsweet/ui' import { compose, @@ -135,22 +135,22 @@ export default compose( if (pendingInvitation.userId === currentUserId) { return <Text ml={1}>Invited</Text> } - if ( - (get(pendingInvitation, 'userId', false) || - get(heInvitation, 'userId', false)) && - (admin || editorInChief) - ) { + const invitedHeId = + get(pendingInvitation, 'userId', false) || + get(heInvitation, 'userId', false) + + if (invitedHeId && (admin || editorInChief)) { const person = chain(handlingEditors) - .filter(he => he.id === get(heInvitation, 'userId', false)) + .filter(he => he.id === invitedHeId) .map(he => ({ ...he, name: `${he.firstName} ${he.lastName}` })) .first() .value() - let invitedPerson = {} + let invitedHe = {} if (get(pendingInvitation, 'userId', false)) { - invitedPerson = pendingInvitation + invitedHe = pendingInvitation } else if (get(heInvitation, 'userId', false)) { - invitedPerson = heInvitation + invitedHe = heInvitation } return ( <PersonInvitation @@ -158,14 +158,16 @@ export default compose( isLatestVersion={isLatestVersion} ml={1} withName - {...invitedPerson} + {...invitedHe} onResend={resendInvitation} onRevoke={revokeInvitation} person={person} /> ) } - + if (!isEmpty(pendingInvitation)) { + return <Text ml={1}>{handlingEditor.name}</Text> + } if (heInvitation) { return <Text ml={1}>{handlingEditor.name}</Text> } @@ -183,7 +185,7 @@ export default compose( </Button> ) } - return <Text ml={1}>Assigned</Text> + return <Text ml={1}>Unassigned</Text> }, }), setDisplayName('ManuscriptHeader'), diff --git a/packages/component-invite/src/routes/collectionsInvitations/delete.js b/packages/component-invite/src/routes/collectionsInvitations/delete.js index 75bc11393f4356d744d9f38f7c4e997db0f129ab..1f4fae26b919dc1a238d387cbf86765860c1030d 100644 --- a/packages/component-invite/src/routes/collectionsInvitations/delete.js +++ b/packages/component-invite/src/routes/collectionsInvitations/delete.js @@ -71,6 +71,21 @@ module.exports = models => async (req, res) => { last(get(collection, 'fragments', [])), ) + const fragmentId = fragment.id + const teamHelperForFragment = new Team({ + TeamModel: models.Team, + collectionId, + fragmentId, + }) + + const teams = await teamHelperForFragment.getTeams('fragment') + const reviewerTeam = teams.find( + team => team.object.id === fragmentId && team.group === 'reviewer', + ) + if (reviewerTeam) { + reviewerTeam.delete() + } + const fileKeys = [] fragment.recommendations && fragment.recommendations.forEach(recommendation => { @@ -99,19 +114,32 @@ module.exports = models => async (req, res) => { await deleteFilesS3({ fileKeys, s3Config }) } + let shouldAuthorBeNotified + if (fragment.invitations.length > 0) { + shouldAuthorBeNotified = true + } + fragment.invitations = [] fragment.recommendations = [] fragment.revision && delete fragment.revision - fragment.save() - } + await fragment.save() - notifications.sendInvitedHEEmail({ - models, - collection, - invitedHE: user, - isCanceled: true, - baseUrl: services.getBaseUrl(req), - }) + if (shouldAuthorBeNotified) { + notifications.notifyAuthorWhenHERemoved({ + models, + collection, + baseUrl: services.getBaseUrl(req), + }) + } + } else { + notifications.sendInvitedHEEmail({ + models, + collection, + invitedHE: user, + isCanceled: true, + baseUrl: services.getBaseUrl(req), + }) + } return res.status(200).json({}) } catch (e) { diff --git a/packages/component-invite/src/routes/collectionsInvitations/emails/emailCopy.js b/packages/component-invite/src/routes/collectionsInvitations/emails/emailCopy.js index c7c665749f3f3bb53a1743c6843753186201620d..b7d6efdea3d2adf90e8182d09698f2e877017124 100644 --- a/packages/component-invite/src/routes/collectionsInvitations/emails/emailCopy.js +++ b/packages/component-invite/src/routes/collectionsInvitations/emails/emailCopy.js @@ -1,6 +1,7 @@ const config = require('config') const staffEmail = config.get('journal.staffEmail') +const journalName = config.get('journal.name') const getEmailCopy = ({ emailType, titleText, targetUserName, comments }) => { let paragraph @@ -34,6 +35,14 @@ const getEmailCopy = ({ emailType, titleText, targetUserName, comments }) => { paragraph = `${targetUserName} has removed you from the role of Handling Editor for ${titleText}.<br/><br/> The manuscript will no longer appear in your dashboard. Please contact ${staffEmail} if you have any questions about this change.` break + case 'author-he-removed': + hasIntro = true + hasLink = false + hasSignature = true + paragraph = `The handling editor of your manuscript "${titleText}" had to be replaced. This may cause some delays in the peer review process.<br/><br/> + If you have questions please email them to ${staffEmail}<br/><br/>. + Thank you for your submission to ${journalName}.` + break default: throw new Error(`The ${emailType} email type is not defined.`) } diff --git a/packages/component-invite/src/routes/collectionsInvitations/emails/notifications.js b/packages/component-invite/src/routes/collectionsInvitations/emails/notifications.js index 2b85380e873f1a686ed362c33c0b991eabac3aef..e17d13c12cf2301effd03495d76a2456e389a91a 100644 --- a/packages/component-invite/src/routes/collectionsInvitations/emails/notifications.js +++ b/packages/component-invite/src/routes/collectionsInvitations/emails/notifications.js @@ -73,6 +73,56 @@ module.exports = { return email.sendEmail() }, + notifyAuthorWhenHERemoved: async ({ + baseUrl, + collection, + models: { User: UserModel, Fragment: FragmentModel }, + }) => { + const fragmentId = last(collection.fragments) + const fragment = await FragmentModel.find(fragmentId) + const fragmentHelper = new Fragment({ fragment }) + const { title: titleText } = await fragmentHelper.getFragmentData() + const { submittingAuthor } = await fragmentHelper.getAuthorData({ + UserModel, + }) + const submittingAuthorName = `${submittingAuthor.firstName} ${ + submittingAuthor.lastName + }` + + const userHelper = new User({ UserModel }) + const eics = await userHelper.getEditorsInChief() + const eic = eics[0] + const eicName = `${eic.firstName} ${eic.lastName}` + const { customId } = collection + + const { paragraph, ...bodyProps } = getEmailCopy({ + titleText, + targetUserName: submittingAuthorName, + emailType: 'author-he-removed', + }) + + const email = new Email({ + type: 'user', + fromEmail: `${eicName} <${staffEmail}>`, + toUser: { + email: submittingAuthor.email, + name: submittingAuthorName, + }, + content: { + subject: `${customId}: Your manuscript's editor was changed`, + paragraph, + signatureName: eicName, + signatureJournal: journalName, + unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, { + id: submittingAuthor.id, + token: submittingAuthor.accessTokens.unsubscribe, + }), + }, + bodyProps, + }) + + return email.sendEmail() + }, sendEiCEmail: async ({ reason, baseUrl, diff --git a/packages/component-manuscript-manager/src/routes/fragments/patch.js b/packages/component-manuscript-manager/src/routes/fragments/patch.js index acd037254ddb9ada6417d99980a2a30588799d08..6a3e11ef06411ccd3868b8da6aca8389289f0d6a 100644 --- a/packages/component-manuscript-manager/src/routes/fragments/patch.js +++ b/packages/component-manuscript-manager/src/routes/fragments/patch.js @@ -24,7 +24,8 @@ module.exports = models => async (req, res) => { fragment = await models.Fragment.find(fragmentId) if (!fragment.revision) { return res.status(400).json({ - error: 'No revision has been found.', + error: + 'Your Handling Editor was changed. A new handling editor will be assigned to your manuscript soon. Sorry for the inconvenience.', }) } diff --git a/packages/component-manuscript-manager/src/tests/fragments/patch.test.js b/packages/component-manuscript-manager/src/tests/fragments/patch.test.js index f4e2147a5604bf4f77e0782fdd1f33cb28ec9f58..be867cbe7df7674d91c619d7837a94ed08e808e6 100644 --- a/packages/component-manuscript-manager/src/tests/fragments/patch.test.js +++ b/packages/component-manuscript-manager/src/tests/fragments/patch.test.js @@ -148,7 +148,9 @@ describe('Patch fragments route handler', () => { expect(res.statusCode).toBe(400) const data = JSON.parse(res._getData()) - expect(data.error).toEqual('No revision has been found.') + expect(data.error).toEqual( + 'Your Handling Editor was changed. A new handling editor will be assigned to your manuscript soon. Sorry for the inconvenience.', + ) }) it('should return an error when the user is inactive', async () => { const { inactiveUser } = testFixtures.users diff --git a/packages/component-manuscript/src/submitRevision/utils.js b/packages/component-manuscript/src/submitRevision/utils.js index bee60515f02ca64d53e90cb8478ebe32de58ea8b..970d5303370d5be965c0c4ad64bae08d7e1ec5d9 100644 --- a/packages/component-manuscript/src/submitRevision/utils.js +++ b/packages/component-manuscript/src/submitRevision/utils.js @@ -5,7 +5,7 @@ import { autosaveRequest } from 'pubsweet-component-wizard/src/redux/autosave' import { submitRevision } from 'pubsweet-component-wizard/src/redux/conversion' const parseRevision = (values, fragment) => ({ - ...fragment, + ...omit(fragment, 'recommendations'), revision: { ...values, }, diff --git a/packages/xpub-faraday/config/authsome-helpers.js b/packages/xpub-faraday/config/authsome-helpers.js index af24148066d5dc53764e440da68fb2c965436f2d..39f753c539a754d22829a00a0397277236eb40b1 100644 --- a/packages/xpub-faraday/config/authsome-helpers.js +++ b/packages/xpub-faraday/config/authsome-helpers.js @@ -4,8 +4,6 @@ const { omit, get, last, chain } = require('lodash') const statuses = config.get('statuses') -const keysToOmit = [`email`, `id`] -const authorCannotViewHENameStatuses = ['heInvited'] const authorAllowedStatuses = ['revisionRequested', 'rejected', 'accepted'] const getTeamsByPermissions = async ( @@ -102,21 +100,27 @@ const filterRecommendationsFromOlderVersions = (recommendations, user) => { const stripeCollectionByRole = ({ collection = {}, role = '' }) => { if (role === 'author') { - const { handlingEditor } = collection - - if (authorCannotViewHENameStatuses.includes(collection.status)) { + if (collection.status === 'heInvited') { return { ...collection, - handlingEditor: handlingEditor && - !handlingEditor.isAccepted && { - ...omit(handlingEditor, keysToOmit), - name: 'Assigned', - }, + handlingEditor: { + name: 'Assigned', + }, + } + } + if (collection.status === 'submitted') { + return { + ...collection, + handlingEditor: { + name: 'Unassigned', + }, } } } + return collection } + const stripeFragmentByRole = ({ fragment = {}, role = '', diff --git a/packages/xpub-faraday/config/authsome-mode.js b/packages/xpub-faraday/config/authsome-mode.js index fe678f981257b9e3a67647f062f3f15ef0f87f31..c9df55d68c485557d2c6031a4a5a35e6f9f752a9 100644 --- a/packages/xpub-faraday/config/authsome-mode.js +++ b/packages/xpub-faraday/config/authsome-mode.js @@ -93,8 +93,13 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) { FragmentModel: context.models.Fragment, }) + const parsedCollection = helpers.stripeCollectionByRole({ + collection, + role, + }) + return { - ...collection, + ...parsedCollection, ...parsedStatuses, fragments: role !== 'reviewer' diff --git a/packages/xpub-faraday/tests/config/authsome-helpers.test.js b/packages/xpub-faraday/tests/config/authsome-helpers.test.js index de16758af67242c6ab707a10a6f0f31f2cb24876..41fa760afb11a7bb0cb7896549aa19151e3ce79a 100644 --- a/packages/xpub-faraday/tests/config/authsome-helpers.test.js +++ b/packages/xpub-faraday/tests/config/authsome-helpers.test.js @@ -19,7 +19,7 @@ describe('Authsome Helpers', () => { expect(newCollection).toBeTruthy() }) - it('Author should not see HE name on dashboard before HE accepts invitation', () => { + it('Author should see Assigned instead of HE name on dashboard before HE accepts invitation', () => { const { collection } = testFixtures.collections collection.handlingEditor = { ...collection.handlingEditor, @@ -86,6 +86,19 @@ describe('Authsome Helpers', () => { const { handlingEditor = {} } = newCollection expect(handlingEditor.name).not.toEqual('Assigned') }) + + it('Author should see Unassigned insted of HE name before HE is invited', () => { + const { collection } = testFixtures.collections + collection.status = 'submitted' + const role = 'author' + const newCollection = ah.stripeCollectionByRole({ + collection, + role, + }) + const { handlingEditor = {} } = newCollection + expect(handlingEditor.name).toEqual('Unassigned') + }) + it('stripeCollection - returns if collection does not have HE', () => { const { collection } = testFixtures.collections delete collection.handlingEditor