diff --git a/README.md b/README.md index 4aa30a201bf65c9b291e92e69846e03502b53862..ca0f91f81e9a6abe154cc83c6bc3f4ddef5ddb21 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -Note: xpub is still _very_ new. This repository contains an initial set of components but is not yet ready for use. ## xPub-faraday @@ -7,13 +6,11 @@ An MVP implementation of the first design sessions which allows a user to go thr ## Roadmap The major tasks we're planning to work on are the following: -* Implement a future-proof theming setup. (#88) -* Let users go through multiple rounds of review. (#50) -* Implement roles and permissions. (#58) -* Change the data model to account for the changes that have occured in `pubsweet-server`, as well as to make it easily portable to Postgres in the future. (#67) -* Merge xpub's authentication, routing and navigation with pubsweet's. (#55 #89 #57) - - +* Implement a future-proof theming setup. +* Let users go through multiple rounds of review. +* Implement roles and permissions. +* Change the data model to account for the changes that have occured in `pubsweet-server`, as well as to make it easily portable to Postgres in the future. +* Merge xpub's authentication, routing and navigation with pubsweet's. You can follow more fine-grained lists of things that we're working on * [Faraday](https://gitlab.coko.foundation/xpub/xpub-faraday/boards) for tasks related to `xpub-faraday` and @@ -21,7 +18,7 @@ You can follow more fine-grained lists of things that we're working on ## Installing -In the root directory, run `yarn` to install all the dependencies. +In the root directory, run `yarn` to install all the dependencies. ## Configuration Add the following values to `packages/xpub-collabra/config/local-development.json` @@ -34,10 +31,16 @@ Add the following values to `packages/xpub-collabra/config/local-development.jso } ``` +xPub-faraday is using external services as AWS, MTS-FTP, Publons, ORCID. In order to run the app locally a `.env` file is mandatory with keys and settings for each service. + +Contact us at technology@hindawi.com for help getting setup. + ## Running the app -1. `cd packages/xpub-faraday` -2. The first time you run the app, initialize the database with `yarn run setupdb` (press Enter when asked for a collection title, to skip that step). +1. Open Docker engine +2. `cd packages/xpub-faraday` +3. start services with `yarn services` +3. The first time you run the app, initialize the database with `yarn run setupdb` (press Enter when asked for a collection title, to skip that step). 3. `yarn start` diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 85a73988173d89e26196b32f9feeadb0ce1bf378..f37f224d533c39270c04975bf4f8b8abcdb92d87 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -53,6 +53,7 @@ const cannotViewReviewersDetails = [ 'submitted', 'heInvited', ] + export const canViewReviewersDetails = (state, collection = {}) => { if (cannotViewReviewersDetails.includes(get(collection, 'status', 'draft'))) { return false @@ -60,6 +61,42 @@ export const canViewReviewersDetails = (state, collection = {}) => { return canViewReports(state, collection.id) } +const canHeViewEditorialCommentsStatuses = [ + 'revisionRequested', + 'rejected', + 'accepted', + 'inQA', + 'pendingApproval', +] +export const canHeViewEditorialComments = (state, collection = {}) => { + const isHE = isHEToManuscript(state, collection.id) + const status = get(collection, 'status', 'draft') + return isHE && canHeViewEditorialCommentsStatuses.includes(status) +} + +const canEICViewEditorialCommentsStatuses = ['rejected', 'accepted', 'inQA'] +export const canEICViewEditorialComments = (state, collection = {}) => { + const isEIC = currentUserIs(state, 'adminEiC') + const status = get(collection, 'status', 'draft') + return isEIC && canEICViewEditorialCommentsStatuses.includes(status) +} + +export const canViewEditorialComments = ( + state, + collection = {}, + fragmentId, +) => { + const editorialRecommentations = getFragmentEditorialComments( + state, + fragmentId, + ) + return ( + (canHeViewEditorialComments(state, collection) || + canEICViewEditorialComments(state, collection)) && + editorialRecommentations.length > 0 + ) +} + export const getUserToken = ({ currentUser }) => get(currentUser, 'user.token', '') @@ -254,6 +291,10 @@ export const getFragmentReviewerRecommendations = (state, fragmentId) => getFragmentRecommendations(state, fragmentId).filter( r => r.recommendationType === 'review', ) +const getFragmentEditorialComments = (state, fragmentId) => + getFragmentRecommendations(state, fragmentId).filter( + r => r.recommendationType === 'editorRecommendation', + ) const getOwnRecommendations = (state, fragmentId) => chain(state) diff --git a/packages/component-faraday-ui/src/EditorialReportCard.js b/packages/component-faraday-ui/src/EditorialReportCard.js index a294c039795914fe5e5333e95b395f938ee4f187..a89ca77499af8c0d0a6b6ceff3b6b5af97c617d5 100644 --- a/packages/component-faraday-ui/src/EditorialReportCard.js +++ b/packages/component-faraday-ui/src/EditorialReportCard.js @@ -9,26 +9,32 @@ import { Label, Item, Row, Text, Tag } from './' import { getReportComments } from './helpers' const EditorialReportCard = ({ + publicLabel, + privateLabel, journal, publicReport, privateReport, recommendation, - reviewerName, - reviewerRole, + editorName, + editorRole, report: { createdOn, reviewer }, }) => ( <Root> <Row justify="space-between" mb={2}> <Item vertical> - <Label mb={1 / 2}>Decision</Label> + {editorRole === 'HE' ? ( + <Label mb={1 / 2}>Recommendation</Label> + ) : ( + <Label mb={1 / 2}>Decision</Label> + )} <Text>{recommendation}</Text> </Item> <Item justify="flex-end"> {reviewer && ( <Fragment> - <Text mr={1 / 2}>{reviewerName}</Text> - <Tag mr={2}>{reviewerRole}</Tag> + <Text mr={1 / 2}>{editorName}</Text> + <Tag mr={2}>{editorRole}</Tag> </Fragment> )} <DateParser timestamp={createdOn}> @@ -40,7 +46,7 @@ const EditorialReportCard = ({ {publicReport && ( <Row mb={2}> <Item vertical> - <Label mb={1 / 2}>Message For Author</Label> + <Label mb={1 / 2}>{publicLabel}</Label> <Text>{publicReport}</Text> </Item> </Row> @@ -49,7 +55,7 @@ const EditorialReportCard = ({ {privateReport && ( <Row mb={2}> <Item vertical> - <Label mb={1 / 2}>Message For Editorial Team</Label> + <Label mb={1 / 2}>{privateLabel}</Label> <Text>{privateReport}</Text> </Item> </Row> @@ -85,8 +91,8 @@ export default compose( recommendation: getRecommendationLabel(), publicReport: getReportComments({ report, isPublic: true }), privateReport: getReportComments({ report, isPublic: false }), - reviewerName: getReviewerName(), - reviewerRole: getReviewerRole(), + editorName: getReviewerName(), + editorRole: getReviewerRole(), }), ), )(EditorialReportCard) diff --git a/packages/component-faraday-ui/src/EditorialReportCard.md b/packages/component-faraday-ui/src/EditorialReportCard.md index 26fd9dce55e7c917e5944b6baf617a3384c50407..0c81711ef2c425f242e7f7b7433347f78de2aa15 100644 --- a/packages/component-faraday-ui/src/EditorialReportCard.md +++ b/packages/component-faraday-ui/src/EditorialReportCard.md @@ -47,7 +47,12 @@ const journal = { }, ], } -;<EditorialReportCard report={report} journal={journal} /> +;<EditorialReportCard + report={report} + journal={journal} + publicLabel="Message For Author" + privateLabel="Message For Editorial Team" + /> ``` Card with message for the editorial team @@ -97,7 +102,12 @@ const journal = { }, ], } -;<EditorialReportCard report={report} journal={journal} /> +;<EditorialReportCard + report={report} + journal={journal} + publicLabel="Message For Author" + privateLabel="Message For Editorial Team" + /> ``` Card with message for the editorial team and for the author @@ -152,5 +162,11 @@ const journal = { }, ], } -;<EditorialReportCard report={report} journal={journal} /> + +;<EditorialReportCard + report={report} + journal={journal} + publicLabel="Message For Author" + privateLabel="Message For Editorial Team" + /> ``` diff --git a/packages/component-faraday-ui/src/PublonsTable.js b/packages/component-faraday-ui/src/PublonsTable.js new file mode 100644 index 0000000000000000000000000000000000000000..ed2ffa47ad5bface693c3f215619034c91ffce86 --- /dev/null +++ b/packages/component-faraday-ui/src/PublonsTable.js @@ -0,0 +1,156 @@ +import React, { Fragment } from 'react' +import { get, last, head } from 'lodash' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { Button, Spinner } from '@pubsweet/ui' +import { compose, withHandlers, withProps } from 'recompose' + +import { Label, OpenModal, Text, withFetching } from '../' + +const TableView = ({ + reviewers, + onInviteReviewer, + setFetching, + isFetching, + publonsError, +}) => { + if (publonsError) { + return ( + <Text align="center" error> + {publonsError} + </Text> + ) + } + return reviewers.length === 0 ? ( + <Text align="center">No suggestions yet.</Text> + ) : ( + <Table> + <thead> + <tr> + <th> + <Label>Full Name</Label> + </th> + <th> + <Label>Affiliation</Label> + </th> + <th> + <Label>No. of Reviews</Label> + </th> + <th> </th> + </tr> + </thead> + <tbody> + {reviewers.map(reviewer => ( + <TableRow key={reviewer.email}> + <td> + <Text>{`${get(reviewer, 'name', '')}`}</Text> + </td> + <td> + <Text>{`${get(reviewer, 'affiliation', '')}`}</Text> + </td> + <td> + <Text>{`${get(reviewer, 'reviews', '')}`}</Text> + </td> + <HiddenCell> + <OpenModal + confirmText="Invite" + isFetching={isFetching} + onConfirm={modalProps => onInviteReviewer(reviewer, modalProps)} + setFetching={setFetching} + title="Send invitation to review?" + > + {showModal => ( + <Button onClick={showModal} primary size="small"> + SEND + </Button> + )} + </OpenModal> + </HiddenCell> + </TableRow> + ))} + </tbody> + </Table> + ) +} + +const PublonsTable = ({ publonsFetching, ...rest }) => ( + <Fragment>{publonsFetching ? <Spinner /> : <TableView {...rest} />}</Fragment> +) + +export default compose( + withFetching, + withProps(({ reviewers = [] }) => ({ + reviewers, + })), + withHandlers({ + onInviteReviewer: ({ onInvite }) => (reviewer, modalProps) => { + const names = reviewer.name.split(' ') + const newReviewer = { + email: reviewer.email, + role: 'reviewer', + firstName: head(names), + lastName: last(names), + } + onInvite(newReviewer, modalProps) + }, + }), +)(PublonsTable) + +// #region styles +const Table = styled.table` + border-collapse: collapse; + + & thead { + border: 1px solid ${th('colorBorder')}; + background-color: ${th('colorBackgroundHue2')}; + padding-top: calc(${th('gridUnit')} * 2); + } + + & th, + & td { + border: none; + padding-left: calc(${th('gridUnit')} * 2); + text-align: start; + vertical-align: middle; + + height: calc(${th('gridUnit')} * 5); + min-width: calc(${th('gridUnit')} * 12); + } +` + +const HiddenCell = styled.td` + opacity: 0; + padding-top: ${th('gridUnit')}; +` + +const HidableCell = styled.td` + opacity: 1; + padding-top: ${th('gridUnit')}; +` + +const TableRow = styled.tr` + background-color: ${th('colorBackgroundHue2')}; + border-bottom: 1px solid ${th('colorBorder')}; + + & td:first-child { + min-width: calc(${th('gridUnit')} * 20); + } + + & td:last-child { + vertical-align: top; + text-align: right; + padding-right: calc(8px * 2); + } + + &:hover { + background: ${th('colorBackgroundHue3')}; + + ${HiddenCell} { + opacity: 1; + } + ${HidableCell} { + opacity: 0; + } + } +` +// #endregion diff --git a/packages/component-faraday-ui/src/PublonsTable.md b/packages/component-faraday-ui/src/PublonsTable.md new file mode 100644 index 0000000000000000000000000000000000000000..f692c2e4740672589b4ff386c06e1f13c180315e --- /dev/null +++ b/packages/component-faraday-ui/src/PublonsTable.md @@ -0,0 +1,39 @@ +A list of publon reviewers. + +```js +const reviewers = [ + { + id: 0, + email: 'email1@email.com', + publishingName: 'Name1', + recentOrganizations: { + name: 'Org1' + }, + numVerifiedReviews: '100' + }, + { + id: 1, + email: 'email2@email.com', + publishingName: 'Name2', + recentOrganizations: { + name: 'Org2' + }, + numVerifiedReviews: '200' + }, + { + id: 2, + email: 'email3@email.com', + publishingName: 'Name3', + recentOrganizations: { + name: 'Org3' + }, + numVerifiedReviews: '300' + }, +]; + +<PublonsTable reviewers={reviewers} onInviteReviwer={(reviewer, modalProps) => { + console.log('the reviewer', reviewer) + + modalProps.setModalError('avem eroare boss') +}}/> +``` diff --git a/packages/component-faraday-ui/src/ReviewersTable.js b/packages/component-faraday-ui/src/ReviewersTable.js index 901e657ebefcad468a49c2801c0485c6a7de8def..9f9e2aaa75309afd25084e94a65cbcf65e2c646b 100644 --- a/packages/component-faraday-ui/src/ReviewersTable.js +++ b/packages/component-faraday-ui/src/ReviewersTable.js @@ -83,7 +83,9 @@ const ReviewersTable = ({ </tbody> </Table> ) : ( - <Text align="center">No reviewers invited yet.</Text> + <Text align="center" pb={2} pt={2}> + No reviewers invited yet. + </Text> ) const orderInvitations = i => { diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js index d64a8a58c009bf3d77b62b2f5e2367b371d389c9..8e242820b8a3077267b79efe84b7355b6d7029d0 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js @@ -12,6 +12,7 @@ import { marginHelper, ContextualBox, ReviewersTable, + PublonsTable, ReviewerReport, InviteReviewers, ReviewerBreakdown, @@ -24,10 +25,14 @@ const ReviewerDetails = ({ reports = [], fragment, invitations, + publonReviewers, + isFetching, previewFile, downloadFile, + fetchingError, canInviteReviewers, onInviteReviewer, + onInvitePublonReviewer, onResendReviewerInvite, onRevokeReviewerInvite, toggle, @@ -59,7 +64,16 @@ const ReviewerDetails = ({ > <H4>Reviewer Details</H4> </TabButton> - + {canInviteReviewers && ( + <TabButton + ml={1} + mr={1} + onClick={() => changeTab(2)} + selected={selectedTab === 2} + > + <H4>Reviewer Suggestions</H4> + </TabButton> + )} <TabButton ml={1} mr={1} @@ -86,6 +100,14 @@ const ReviewerDetails = ({ /> </Fragment> )} + {selectedTab === 2 && ( + <PublonsTable + onInvite={onInvitePublonReviewer} + publonsError={fetchingError} + publonsFetching={isFetching} + reviewers={publonReviewers} + /> + )} {selectedTab === 1 && ( <Fragment> {reports.length === 0 && ( @@ -114,14 +136,22 @@ const ReviewerDetails = ({ export default compose( withFilePreview, withFileDownload, - withProps(({ invitations = [], reviewerReports = [], currentUser }) => ({ - token: get(currentUser, 'token', ''), - invitations: invitations.map(i => ({ - ...i, - review: reviewerReports.find(r => r.userId === i.userId), - })), - reports: reviewerReports.filter(r => r.submittedOn), - })), + withProps( + ({ + invitations = [], + publonReviewers = [], + reviewerReports = [], + currentUser, + }) => ({ + token: get(currentUser, 'token', ''), + publonReviewers, + invitations: invitations.map(i => ({ + ...i, + review: reviewerReports.find(r => r.userId === i.userId), + })), + reports: reviewerReports.filter(r => r.submittedOn), + }), + ), withProps(({ currentUser }) => ({ canInviteReviewers: get(currentUser, 'permissions.canInviteReviewers'), canViewReviewersDetails: get( diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 9c505e4d598f0482be74d0c8d9de774572327b8d..263820a923f836e555affe74bed8bd51d6ea7b6d 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -30,6 +30,7 @@ export { default as Pagination } from './Pagination' export { default as PersonInfo } from './PersonInfo' export { default as PersonInvitation } from './PersonInvitation' export { default as PreviewFile } from './PreviewFile' +export { default as PublonsTable } from './PublonsTable' export { default as RadioWithComments } from './RadioWithComments' export { default as ReviewerReport } from './ReviewerReport' export { default as ReviewersTable } from './ReviewersTable' diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js index a6f66d3736b655bb1835539cd73115feac761ceb..1c40f7107e6358774fcac738b4e62cb0774b16a4 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js @@ -1,6 +1,6 @@ import React from 'react' -import { get } from 'lodash' -import { compose } from 'recompose' +import { get, last } from 'lodash' +import { compose, withProps } from 'recompose' import styled from 'styled-components' import { reduxForm } from 'redux-form' import { th } from '@pubsweet/ui-toolkit' @@ -19,12 +19,34 @@ import { withFetching, } from '../' +const eicDecisions = [ + { + value: 'return-to-handling-editor', + label: 'Return to Handling Editor', + modalTitle: 'Return Manuscript', + modalSubtitle: + 'A returning manuscript to Handling Editor decision is final', + }, + { + value: 'publish', + label: 'Publish', + modalTitle: 'Publish Manuscript', + modalSubtitle: 'A publish decision is final', + }, + { + value: 'reject', + label: 'Reject', + modalTitle: 'Reject Manuscript', + modalSubtitle: 'A rejection decision is final', + }, +] + const ManuscriptEicDecision = ({ isFetching, formValues, handleSubmit, messagesLabel, - options = [], + collection = {}, ...rest }) => ( <ContextualBox label="Your Editorial Decision" {...rest}> @@ -33,29 +55,36 @@ const ManuscriptEicDecision = ({ <ItemOverrideAlert flex={0} vertical> <Label required>Decision</Label> <ValidatedField - component={input => <Menu {...input} options={options} />} + component={input => ( + <Menu + {...input} + options={ + get(collection, 'status', 'submitted') === 'submitted' + ? [last(eicDecisions)] + : eicDecisions + } + /> + )} name="decision" validate={[required]} /> </ItemOverrideAlert> </Row> - <Row mt={2}> - <Item vertical> - <Label required> - { - messagesLabel[ - get(formValues, 'decision', 'return-to-handling-editor') - ] - } - </Label> - <ValidatedField - component={ValidatedTextArea} - name="message" - validate={[required]} - /> - </Item> - </Row> + {get(formValues, 'decision') !== 'publish' && ( + <Row mt={2}> + <Item vertical> + <Label required> + {messagesLabel[get(formValues, 'decision', 'reject')]} + </Label> + <ValidatedField + component={ValidatedTextArea} + name="message" + validate={[required]} + /> + </Item> + </Row> + )} <Row justify="flex-end" mt={4}> <Button onClick={handleSubmit} primary size="medium"> @@ -73,15 +102,32 @@ export default compose( modalKey: 'eic-decision', modalComponent: MultiAction, })), + withProps(({ formValues }) => ({ + modalTitle: eicDecisions.find( + o => o.value === get(formValues, 'decision', 'publish'), + ).modalTitle, + modalSubtitle: eicDecisions.find( + o => o.value === get(formValues, 'decision', 'publish'), + ).modalSubtitle, + })), reduxForm({ form: 'eic-decision', onSubmit: ( values, dispatch, - { submitDecision, showModal, setFetching }, + { + submitDecision, + showModal, + setFetching, + modalTitle, + confirmMessage, + modalSubtitle, + }, ) => { showModal({ - title: 'Are you sure you want to submit this decision?', + title: `${modalTitle}?`, + confirmText: modalTitle, + subtitle: modalSubtitle, onConfirm: modalProps => { submitDecision(values, { ...modalProps, setFetching }) }, diff --git a/packages/component-faraday-ui/src/modals/MultiAction.js b/packages/component-faraday-ui/src/modals/MultiAction.js index a1beeaf592edb16b9df1a02bdab80e24027c9fab..f441ad6ec0ac2d61aaa4fa968ac4d7648ef26d38 100644 --- a/packages/component-faraday-ui/src/modals/MultiAction.js +++ b/packages/component-faraday-ui/src/modals/MultiAction.js @@ -20,10 +20,14 @@ const MultiAction = ({ <Root> <IconButton icon="x" onClick={onClose} right={5} secondary top={5} /> <H2>{title}</H2> - {subtitle && <Text secondary>{subtitle}</Text>} + {subtitle && ( + <Text mb={1} secondary> + {subtitle} + </Text> + )} {renderContent()} {modalError && ( - <Text error mt={1}> + <Text align="center" error mt={1}> {modalError} </Text> )} @@ -79,7 +83,7 @@ const Root = styled.div` flex-direction: column; position: relative; padding: calc(${th('gridUnit')} * 5); - width: calc(${th('gridUnit')} * 60); + width: calc(${th('gridUnit')} * 70); ${H2} { margin: 0 0 ${th('gridUnit')} 0; diff --git a/packages/component-invite/src/FragmentsInvitations.js b/packages/component-invite/src/FragmentsInvitations.js index 7a4d2507424cbeb5a5a0c0357779508fed009a8d..7e01cf30948ac41226e63f44571199461c0f106a 100644 --- a/packages/component-invite/src/FragmentsInvitations.js +++ b/packages/component-invite/src/FragmentsInvitations.js @@ -16,7 +16,12 @@ const FragmentsInvitations = app => { * @apiParamExample {json} Body * { * "email": "email@example.com", - * "role": "reviewer", [acceptedValues: reviewer] + * "role": "reviewer", [acceptedValues: reviewer], + * "firstName": "Julien", + * "lastName": "Hopfenkonig", + * "affiliation": "UCLA", + * "country": "RO" + * "isPublons": false [Boolean] * } * @apiSuccessExample {json} Success * HTTP/1.1 200 OK @@ -51,8 +56,8 @@ const FragmentsInvitations = app => { * HTTP/1.1 200 OK * [{ * "name": "John Smith", - * "invitedOn": 1525428890167, - * "respondedOn": 1525428890299, + * "invitedOn": 1525428890167, + * "respondedOn": 1525428890299, * "email": "email@example.com", * "status": "pending", * "invitationId": "1990881" diff --git a/packages/component-invite/src/routes/fragmentsInvitations/post.js b/packages/component-invite/src/routes/fragmentsInvitations/post.js index fada049c6d8c93f35bc02999e4f79b640c9ed399..a79f02e945009b3027fcffd4d85e52f636af5315 100644 --- a/packages/component-invite/src/routes/fragmentsInvitations/post.js +++ b/packages/component-invite/src/routes/fragmentsInvitations/post.js @@ -129,10 +129,33 @@ module.exports = models => async (req, res) => { } catch (e) { const userHelper = new User({ UserModel }) - const newUser = await userHelper.createUser({ - role, - body: req.body, - }) + const userData = req.body + const { firstName = '', lastName = '', isPublons } = userData + if (!services.checkForUndefinedParams(firstName, lastName)) { + return res + .status(400) + .json({ error: 'First name and last name are required.' }) + } + + if (isPublons && process.env.PUBLONS_MOCK_EMAIL) { + const mockEmail = process.env.PUBLONS_MOCK_EMAIL + userData.email = mockEmail.replace( + '__NAME__', + `${firstName.trim()}.${lastName.trim()}`, + ) + } + + let newUser + try { + newUser = await userHelper.createUser({ + role, + body: userData, + }) + } catch (e) { + return res + .status(400) + .json({ error: `User already exists with email: ${userData.email}` }) + } if (collection.status === 'heAssigned') await collectionHelper.updateStatus({ newStatus: 'reviewersInvited' }) diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/notifications.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/notifications.js index eee5ba2fc9ee4eb1206172c29b7d6c298dfeeabd..9bb9c3a6cbd7d7b66222cb4b472e108c08304a06 100644 --- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/notifications.js +++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/notifications/notifications.js @@ -60,7 +60,11 @@ module.exports = { // the EiC recommends to publish so an email to the EQA needs to be sent, // one requesting approval or one informing them that the manuscript has been published - if (isEditorInChief && recommendation === 'publish') { + if ( + isEditorInChief && + recommendation === 'publish' && + collection.technicalChecks.token + ) { sendEQAEmail({ email, eicName, @@ -77,7 +81,6 @@ module.exports = { // send HE emails when a review is submitted // or when the EiC makes a recommendation after peer review - if ( recommendationType === 'review' || (isEditorInChief && diff --git a/packages/component-manuscript-manager/src/routes/technicalChecks/notifications/notifications.js b/packages/component-manuscript-manager/src/routes/technicalChecks/notifications/notifications.js index 631f6719315bd164636fdb88b9632caa4c08d15a..8380da030638ce5c7d66293fd7531ac01841a237 100644 --- a/packages/component-manuscript-manager/src/routes/technicalChecks/notifications/notifications.js +++ b/packages/component-manuscript-manager/src/routes/technicalChecks/notifications/notifications.js @@ -1,5 +1,4 @@ const config = require('config') -const { get } = require('lodash') const Email = require('@pubsweet/component-email-templating') const { @@ -9,12 +8,10 @@ const { } = require('pubsweet-component-helper-service') const { getEmailCopy } = require('./emailCopy') -const unsubscribeSlug = config.get('unsubscribe.url') const { name: journalName, staffEmail } = config.get('journal') module.exports = { async sendNotifications({ - isEQA, agree, baseUrl, collection, @@ -28,10 +25,7 @@ module.exports = { handlingEditor: collection.handlingEditor, }) - const { - activeAuthors: authors, - submittingAuthor, - } = await fragmentHelper.getAuthorData({ + const { submittingAuthor } = await fragmentHelper.getAuthorData({ UserModel, }) @@ -60,42 +54,14 @@ module.exports = { }, }) - if (agree && isEQA) { - email.content.signatureName = await userHelper.getEiCName() - - sendHandlingEditorEmail({ - email, - baseUrl, - titleText, - subjectBaseText, - handlingEditor: get(collection, 'handlingEditor', {}), - }) - sendSubmittedReviewersEmail({ - email, - baseUrl, - titleText, - UserModel, - fragmentHelper, - subjectBaseText, - }) - - sendAuthorsEmail({ - email, - baseUrl, - titleText, - subjectBaseText, - fragmentAuthors: authors, - }) - } else { - sendEditorsEmail({ - email, - agree, - customId, - comments, - titleText, - userHelper, - }) - } + sendEditorsEmail({ + email, + agree, + customId, + comments, + titleText, + userHelper, + }) }, } @@ -137,113 +103,3 @@ const sendEditorsEmail = async ({ email.sendEmail({ html, text }) }) } - -const sendHandlingEditorEmail = ({ - email, - baseUrl, - titleText, - handlingEditor, - subjectBaseText, -}) => { - email.content.subject = `${subjectBaseText} Decision` - const emailType = 'he-manuscript-published' - - email.toUser = { - email: handlingEditor.email, - name: handlingEditor.name, - } - email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, { - id: handlingEditor.id, - }) - - const { html, text } = email.getNotificationBody({ - emailBodyProps: getEmailCopy({ - emailType, - titleText, - }), - }) - email.sendEmail({ html, text }) -} - -const sendSubmittedReviewersEmail = async ({ - email, - baseUrl, - titleText, - UserModel, - fragmentHelper, - subjectBaseText, -}) => { - email.content.subject = `${subjectBaseText} Decision` - - const reviewers = (await fragmentHelper.getReviewers({ - UserModel, - type: 'submitted', - })).map(rev => ({ - ...rev, - ...getEmailCopy({ - emailType: 'submitted-reviewers-after-publish', - titleText, - }), - })) - - reviewers.forEach(reviewer => { - email.toUser = { - email: reviewer.email, - name: `${reviewer.firstName} ${reviewer.lastName}`, - } - email.content.unsubscribeLink = services.createUrl( - baseUrl, - unsubscribeSlug, - { - id: reviewer.id, - }, - ) - const { html, text } = email.getNotificationBody({ - emailBodyProps: { - paragraph: reviewer.paragraph, - hasLink: reviewer.hasLink, - }, - }) - email.sendEmail({ html, text }) - }) -} - -const sendAuthorsEmail = ({ - email, - baseUrl, - titleText, - subjectBaseText, - fragmentAuthors, -}) => { - const emailType = 'author-manuscript-published' - email.content.subject = `${subjectBaseText} Published` - - const authors = fragmentAuthors.map(author => ({ - ...author, - ...getEmailCopy({ - emailType, - titleText, - }), - })) - - authors.forEach(author => { - email.toUser = { - email: author.email, - name: `${author.firstName} ${author.lastName}`, - } - email.content.unsubscribeLink = services.createUrl( - baseUrl, - unsubscribeSlug, - { - id: author.id, - }, - ) - const { html, text } = email.getNotificationBody({ - emailBodyProps: { - paragraph: author.paragraph, - hasLink: author.hasLink, - }, - }) - email.sendEmail({ html, text }) - }) -} diff --git a/packages/component-manuscript-manager/src/routes/technicalChecks/patch.js b/packages/component-manuscript-manager/src/routes/technicalChecks/patch.js index 4afb91c9d628e6a13e19bfea7d2360527dab8ab5..fcb8a59daf70b1bc7129fa23b1fe664d8de5750e 100644 --- a/packages/component-manuscript-manager/src/routes/technicalChecks/patch.js +++ b/packages/component-manuscript-manager/src/routes/technicalChecks/patch.js @@ -1,7 +1,13 @@ -const { get, find, isEmpty } = require('lodash') +const { get, find, isEmpty, last } = require('lodash') const { services } = require('pubsweet-component-helper-service') -const { sendNotifications } = require('./notifications/notifications') +const { + sendNotifications: sendEQSNotifications, +} = require('./notifications/notifications') + +const { + sendNotifications: sendEQANotifications, +} = require('../fragmentsRecommendations/notifications/notifications') const TECHNICAL_STEPS = { EQS: 'eqs', @@ -64,15 +70,33 @@ module.exports = ({ Collection, Fragment, User }) => async (req, res) => { collection.status = setNewStatus(step, agree) await collection.save() - sendNotifications({ - User, - agree, - comments, - Fragment, - collection, - baseUrl: services.getBaseUrl(req), - isEQA: step === TECHNICAL_STEPS.EQA, - }) + const isEQA = get(collection, 'technicalChecks.eqa', false) + const baseUrl = services.getBaseUrl(req) + if (isEQA) { + const fragment = await Fragment.find(last(collection.fragments)) + + sendEQANotifications({ + baseUrl, + fragment, + collection, + hasEQA: true, + UserModel: User, + isEditorInChief: true, + newRecommendation: { + recommendation: 'publish', + recommendationType: 'editorRecommendation', + }, + }) + } else { + sendEQSNotifications({ + User, + agree, + comments, + Fragment, + collection, + baseUrl, + }) + } return res.status(200).json(collection) } catch (e) { diff --git a/packages/component-manuscript/src/components/EditorialCommentCard.js b/packages/component-manuscript/src/components/EditorialCommentCard.js index aa9187971fa82db28b3a3896b7fafb78c4520622..366200e4fba723bc2c2a7431b4d19b39ea0501b9 100644 --- a/packages/component-manuscript/src/components/EditorialCommentCard.js +++ b/packages/component-manuscript/src/components/EditorialCommentCard.js @@ -6,10 +6,22 @@ import { withFileDownload, } from 'pubsweet-component-faraday-ui' -const EditorialCommentCard = ({ journal, reports = [] }) => ( - <ContextualBox label="Editorial Comments" mb={2}> +const EditorialCommentCard = ({ journal, reports = [], toggle, expanded }) => ( + <ContextualBox + expanded={expanded} + label="Editorial Comments" + mb={2} + scrollIntoView + toggle={toggle} + > {reports.map(report => ( - <EditorialReportCard journal={journal} key={report.id} report={report} /> + <EditorialReportCard + journal={journal} + key={report.id} + privateLabel="Message For Editorial Team" + publicLabel="Message For Author" + report={report} + /> ))} </ContextualBox> ) diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index fc006093cb6812da5384997a07d97dba965ed1f3..6c1cfc840e633a7eb86a685e7b0f7f2316eae15c 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -1,6 +1,6 @@ import React, { Fragment } from 'react' import styled from 'styled-components' -import { isEmpty, get, last } from 'lodash' +import { isEmpty, get } from 'lodash' import { Text, paddingHelper, @@ -18,15 +18,8 @@ import ReviewerReportCard from './ReviewReportCard' import ReviewerReportForm from './ReviewerReportForm' import EditorialCommentCard from './EditorialCommentCard' -const eicDecisions = [ - { value: 'return-to-handling-editor', label: 'Return to Handling Editor' }, - { value: 'publish', label: 'Publish' }, - { value: 'reject', label: 'Reject' }, -] - const messagesLabel = { 'return-to-handling-editor': 'Comments for Handling Editor', - publish: 'Comments for Author', reject: 'Comments for Author', } @@ -45,6 +38,7 @@ const ManuscriptLayout = ({ fragment = {}, changeForm, isFetching, + fetchingError, formValues, heExpanded, onHEResponse, @@ -57,18 +51,21 @@ const ManuscriptLayout = ({ onRevokeReviewerInvite, toggleReviewerResponse, invitationsWithReviewers, + publonReviewers, reviewerResponseExpanded, pendingOwnRecommendation, toggleReviewerRecommendations, reviewerRecommendationExpanded, shouldReview, submittedOwnRecommendation, - heAccepted, reviewerReports, onEditorialRecommendation, reviewerRecommendations, toggleReviewerDetails, reviewerDetailsExpanded, + toggleHeRecommendation, + heRecommendationExpanded, + onInvitePublonReviewer, }) => ( <Root pb={30}> {!isEmpty(collection) && !isEmpty(fragment) ? ( @@ -100,13 +97,14 @@ const ManuscriptLayout = ({ getSignedUrl={getSignedUrl} /> - {get(currentUser, 'permissions.canViewReports', true) && - !!editorialRecommendations.length && ( - <EditorialCommentCard - journal={journal} - reports={editorialRecommendations} - /> - )} + {get(currentUser, 'permissions.canViewEditorialComments', true) && ( + <EditorialCommentCard + expanded={heRecommendationExpanded} + journal={journal} + reports={editorialRecommendations} + toggle={toggleHeRecommendation} + /> + )} {submittedOwnRecommendation && ( <ReviewerReportCard @@ -166,15 +164,19 @@ const ManuscriptLayout = ({ <ReviewerDetails currentUser={currentUser} expanded={reviewerDetailsExpanded} + fetchingError={fetchingError} fragment={fragment} getSignedUrl={getSignedUrl} highlight={reviewerReports.length === 0} invitations={invitationsWithReviewers} + isFetching={isFetching.publonsFetching} journal={journal} mb={2} + onInvitePublonReviewer={onInvitePublonReviewer} onInviteReviewer={onInviteReviewer} onResendReviewerInvite={onResendReviewerInvite} onRevokeReviewerInvite={onRevokeReviewerInvite} + publonReviewers={publonReviewers} reviewerReports={reviewerReports} scrollIntoView toggle={toggleReviewerDetails} @@ -195,14 +197,11 @@ const ManuscriptLayout = ({ {get(currentUser, 'permissions.canMakeDecision', false) && ( <ManuscriptEicDecision + collection={collection} formValues={get(formValues, 'eicDecision')} + highlight={editorialRecommendations.length > 0} messagesLabel={messagesLabel} mt={2} - options={ - get(collection, 'status', 'submitted') === 'submitted' - ? [last(eicDecisions)] - : eicDecisions - } submitDecision={createRecommendation} /> )} diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index e24d81b07432d510a7285a26c3a344092371c9e9..a60efec18e8696df3987e8fcbf0b02f94316a77f 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -48,6 +48,7 @@ import { currentUserIsReviewer, parseCollectionDetails, canMakeHERecommendation, + canViewEditorialComments, canViewReviewersDetails, pendingReviewerInvitation, canOverrideTechnicalChecks, @@ -56,10 +57,19 @@ import { getFragmentReviewerRecommendations, getInvitationsWithReviewersForFragment, } from 'pubsweet-component-faraday-selectors' -import { RemoteOpener, handleError } from 'pubsweet-component-faraday-ui' +import { + RemoteOpener, + handleError, + withFetching, +} from 'pubsweet-component-faraday-ui' import ManuscriptLayout from './ManuscriptLayout' -import { parseEicDecision, parseSearchParams, redirectToError } from './utils' +import { + parseEicDecision, + parseSearchParams, + redirectToError, + getPublonsReviewers, +} from './utils' import { canAssignHE, selectFetching, @@ -74,6 +84,8 @@ export default compose( setDisplayName('ManuscriptPage'), withJournal, withRouter, + withFetching, + withState('publonReviewers', 'setPublonsReviewers', []), withState('editorInChief', 'setEiC', 'N/A'), ConnectPage(({ match }) => [ actions.getCollection({ id: match.params.project }), @@ -137,6 +149,7 @@ export default compose( pendingHEInvitation, pendingOwnRecommendation, pendingReviewerInvitation, + isFetching, }, ) => ({ currentUser: { @@ -155,6 +168,11 @@ export default compose( }), canAssignHE: canAssignHE(state, match.params.project), canViewReports: canViewReports(state, match.params.project), + canViewEditorialComments: canViewEditorialComments( + state, + collection, + match.params.version, + ), canInviteReviewers: canInviteReviewers(state, collection), canMakeRecommendation: !isUndefined(pendingOwnRecommendation), canMakeRevision: canMakeRevision(state, collection, fragment), @@ -166,6 +184,7 @@ export default compose( }, isFetching: { editorsFetching: selectFetching(state), + publonsFetching: isFetching, }, formValues: { eicDecision: getFormValues('eic-decision')(state), @@ -197,6 +216,22 @@ export default compose( getFragment(collection, fragment) getUsers() }, + getPublonsReviewers: ({ clearError, setFetching, setPublonsReviewers }) => ( + fragmentId, + errorFn, + ) => { + clearError() + setFetching(true) + getPublonsReviewers(fragmentId) + .then(res => { + setFetching(false) + setPublonsReviewers(res) + }) + .catch(e => { + setFetching(false) + handleError(errorFn)(e) + }) + }, }), withHandlers({ updateManuscript: ({ updateVersion, collection, fragment }) => data => @@ -404,6 +439,35 @@ export default compose( }) }, }), + withHandlers({ + onInvitePublonReviewer: ({ + setError, + fragment, + collection, + clearError, + getPublonsReviewers, + fetchUpdatedCollection, + setFetching: setListFetching, + }) => (reviewerData, { hideModal, setModalError, setFetching }) => { + setFetching(true) + inviteReviewer({ + reviewerData, + isPublons: true, + fragmentId: fragment.id, + collectionId: collection.id, + }) + .then(() => { + setFetching(false) + hideModal() + fetchUpdatedCollection() + getPublonsReviewers(fragment.id) + }) + .catch(err => { + setFetching(false) + handleError(setModalError)(err) + }) + }, + }), fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({ toggleAssignHE: toggle, heExpanded: expanded, @@ -424,6 +488,10 @@ export default compose( toggleReviewerDetails: toggle, reviewerDetailsExpanded: expanded, })), + fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({ + toggleHeRecommendation: toggle, + heRecommendationExpanded: expanded, + })), withProps(({ currentUser, collection, submittedOwnRecommendation }) => ({ getSignedUrl, shouldReview: @@ -435,15 +503,25 @@ export default compose( const { match, history, + setError, location, shouldReview, reviewerReports, setEditorInChief, clearCustomError, + getPublonsReviewers, hasManuscriptFailure, fetchUpdatedCollection, - currentUser: { isInvitedHE, isInvitedToReview, isHEToManuscript }, + editorialRecommendations, + currentUser: { + isInvitedHE, + isInvitedToReview, + isHEToManuscript, + isEIC, + permissions: { canInviteReviewers }, + }, } = this.props + if (hasManuscriptFailure) { history.push('/not-found') clearCustomError() @@ -463,6 +541,10 @@ export default compose( setEditorInChief(head(res.users)), ) + if (canInviteReviewers) { + getPublonsReviewers(fragmentId, setError) + } + if (isInvitedHE) { this.props.toggleHEResponse() } @@ -478,6 +560,10 @@ export default compose( if (isHEToManuscript && !!reviewerReports.length) { this.props.toggleReviewerDetails() } + + if (isEIC && !!editorialRecommendations.length) { + this.props.toggleHeRecommendation() + } }, }), )(ManuscriptLayout) diff --git a/packages/component-manuscript/src/components/utils.js b/packages/component-manuscript/src/components/utils.js index 4a3a4edef0f9f15156c27517ca4c0577ffd6b608..8d9a2d6cc3ad46d4304e2fc07dcfb57731c49c3b 100644 --- a/packages/component-manuscript/src/components/utils.js +++ b/packages/component-manuscript/src/components/utils.js @@ -13,6 +13,7 @@ import { change as changeForm } from 'redux-form' import { actions } from 'pubsweet-client/src' import { handleError } from 'pubsweet-component-faraday-ui' +import { get as apiGet } from 'pubsweet-client/src/helpers/api' import { autosaveRequest, @@ -210,6 +211,7 @@ export const onReviewSubmit = ( collectionId: project.id, recommendation: newValues, }) + .then(() => dispatch(actions.getCollection({ id: project.id }))) .then(() => { dispatch(actions.getFragments({ id: project.id })) hideModal() @@ -275,7 +277,6 @@ export const onRevisionSubmit = ( onCancel: hideModal, }) } - // revision validators export const requiredHTML = value => { if (value && value.replace('<p></p>', '').replace('<h1></h1>', '')) { @@ -303,3 +304,7 @@ export const parseEicDecision = ({ decision, message }) => ({ }, ], }) + +// handle publons +export const getPublonsReviewers = fragmentId => + apiGet(`/fragments/${fragmentId}/publons`) diff --git a/packages/component-publons/src/routes/publons/get.js b/packages/component-publons/src/routes/publons/get.js index 2dcf6739e57c40f2626618b87560be3500564222..5335e50a30bce8def83c53daa5bc45b9cf72f0cc 100644 --- a/packages/component-publons/src/routes/publons/get.js +++ b/packages/component-publons/src/routes/publons/get.js @@ -40,7 +40,8 @@ module.exports = models => async (req, res) => { fragmentId: fragment.id, }) const parsedFragment = await fragmentHelper.getFragmentData() - const memberIds = await teamHelper.getTeamMembers({ + let memberIds = [] + memberIds = await teamHelper.getTeamMembers({ role: 'reviewer', objectType: 'fragment', }) diff --git a/packages/component-wizard/src/components/SubmissionStatement.js b/packages/component-wizard/src/components/SubmissionStatement.js index cdbc630458e7808c29651a8bd83520d476f30301..4769604cea17f535dd3b4fb239361d5bdc8b154d 100644 --- a/packages/component-wizard/src/components/SubmissionStatement.js +++ b/packages/component-wizard/src/components/SubmissionStatement.js @@ -3,23 +3,13 @@ import { ActionLink, Text, Row } from 'pubsweet-component-faraday-ui' const SubmissionStatement = () => ( <Fragment> - <Row mb={1}> + <Row mb={1} mt={2}> <Text> This manuscript is not currently submitted to or under consideration in any other journals. </Text> </Row> - <Row mb={1}> - <Text> - The submission ensures that sources are given proper attribution (the - journal employs <b>Crossref Similarity Check</b> to compare submissions - against published scholarly content. If, in the judgement of an editor, - a submission is genuinely suspected of plagiarism, it will be returned - to the author(s) with a request for explanation). - </Text> - </Row> - <Row mb={1}> <Text> The manuscript complies with all relevant{' '} @@ -29,28 +19,13 @@ const SubmissionStatement = () => ( </Text> </Row> - <Row mb={1}> - <Text> - If applicable - there is a Data Availability statement, containing - information about the location of any open data and materials in the - manuscript, and how others can access the data. - </Text> - </Row> - - <Row mb={1}> - <Text> - A conflict of interest statement is present in the manuscript, even if - to state there is no conflict of interest. - </Text> - </Row> - - <Row mb={1}> + <Row mb={3}> <Text> You have read and understood the{' '} <ActionLink to="https://www.hindawi.com/terms/"> terms of service - </ActionLink> - {' & '} + </ActionLink>{' '} + and{' '} <ActionLink to="https://www.hindawi.com/privacy/"> privacy policy </ActionLink>{' '} diff --git a/packages/components-faraday/src/components/MakeDecision/DecisionForm.js b/packages/components-faraday/src/components/MakeDecision/DecisionForm.js index 3fab58f21c15bcdd312cc0fa0f8df64ef928350e..b698cf1f41d8b9c2fac0ba97fa7b01f588eac0ce 100644 --- a/packages/components-faraday/src/components/MakeDecision/DecisionForm.js +++ b/packages/components-faraday/src/components/MakeDecision/DecisionForm.js @@ -73,7 +73,7 @@ const DecisionForm = ({ {decision === 'return-to-handling-editor' && ( <Row> <RowItem vertical> - <Label>Comments for Handling Editor</Label> + <Label>Comments for Author</Label> <ValidatedField component={TextAreaField} name="messageToHE" diff --git a/packages/components-faraday/src/components/SignUp/SignUpStep0.js b/packages/components-faraday/src/components/SignUp/SignUpStep0.js index 0c7e9ec11627bbee30c7d934bf6b4e458cd84a19..51edb75af1575d53e615c6cff9db1f7081b682ca 100644 --- a/packages/components-faraday/src/components/SignUp/SignUpStep0.js +++ b/packages/components-faraday/src/components/SignUp/SignUpStep0.js @@ -4,6 +4,7 @@ import { reduxForm } from 'redux-form' import { get, isUndefined } from 'lodash' import { required as requiredValidator } from 'xpub-validators' import { Menu, Button, Checkbox, TextField, ValidatedField } from '@pubsweet/ui' +import styled from 'styled-components' import { Row, @@ -16,7 +17,7 @@ import { } from 'pubsweet-component-faraday-ui' const AgreeCheckbox = ({ value, onChange }) => ( - <Row alignItems="center" justify="flex-start" mb={4} mt={3}> + <Row alignItems="center" justify="flex-start" mb={3} mt={3}> <Checkbox checked={value} onChange={onChange} value={value} /> <Text> I agree with the{' '} @@ -26,6 +27,9 @@ const AgreeCheckbox = ({ value, onChange }) => ( </Text> </Row> ) +const ValidationRow = styled(Row)` + justify-content: left; +` const Step0 = ({ type, @@ -98,11 +102,13 @@ const Step0 = ({ </Item> </Row> - <ValidatedField - component={AgreeCheckbox} - name="agreeTC" - validate={[requiredValidator]} - /> + <ValidationRow mb={2}> + <ValidatedField + component={AgreeCheckbox} + name="agreeTC" + validate={[requiredValidator]} + /> + </ValidationRow> <Row> <Text secondary small> diff --git a/packages/components-faraday/src/redux/reviewers.js b/packages/components-faraday/src/redux/reviewers.js index 7542b980da4cdb1aaab21256f0b85bca2cabfb3d..4cb9dd209e6d8d922a0e3ca394698743754b61c2 100644 --- a/packages/components-faraday/src/redux/reviewers.js +++ b/packages/components-faraday/src/redux/reviewers.js @@ -59,10 +59,16 @@ export const getCollectionReviewers = (collectionId, fragmentId) => dispatch => ) // #endregion -export const inviteReviewer = ({ reviewerData, collectionId, fragmentId }) => +export const inviteReviewer = ({ + fragmentId, + reviewerData, + collectionId, + isPublons = false, +}) => create(`/collections/${collectionId}/fragments/${fragmentId}/invitations`, { ...reviewerData, role: 'reviewer', + isPublons, }) // #region Actions - invitations diff --git a/packages/xpub-faraday/config/authsome-helpers.js b/packages/xpub-faraday/config/authsome-helpers.js index f44cf22496e43c2a4428dfa51e2ee4acf99c0d91..c97f8b89a8f9acb1b7b626e5fc5b79636889bf65 100644 --- a/packages/xpub-faraday/config/authsome-helpers.js +++ b/packages/xpub-faraday/config/authsome-helpers.js @@ -180,6 +180,17 @@ const stripeFragmentByRole = ({ ? recommendations.filter(r => r.userId === user.id) : [], } + case 'handlingEditor': + return { + ...fragment, + recommendations: recommendations + ? recommendations.filter( + r => + r.submittedOn || + r.recommendationType === 'editorRecommendation', + ) + : [], + } default: return fragment } diff --git a/packages/xpub-faraday/tests/config/authsome-helpers.test.js b/packages/xpub-faraday/tests/config/authsome-helpers.test.js index 82e6978c47a3d5f509dae9d86b9f14d6b377eccd..58aa8b2b798660dda269a16c8819a9126adf24c0 100644 --- a/packages/xpub-faraday/tests/config/authsome-helpers.test.js +++ b/packages/xpub-faraday/tests/config/authsome-helpers.test.js @@ -88,81 +88,129 @@ describe('Authsome Helpers', () => { expect(result.handlingEditor).toBeFalsy() }) - it('stripeFragment - reviewer should not see authors email', () => { - const { fragment } = testFixtures.fragments - const result = ah.stripeFragmentByRole({ fragment, role: 'reviewer' }) - const { authors = [] } = result - expect(authors[0].email).toBeFalsy() - }) - it('stripeFragment - other roles than reviewer should see authors emails', () => { - const { fragment } = testFixtures.fragments - const result = ah.stripeFragmentByRole({ fragment, role: 'author' }) - const { authors = [] } = result + describe('stripeFragmentByRole', () => { + it('reviewer should not see authors email', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole({ fragment, role: 'reviewer' }) + const { authors = [] } = result + expect(authors[0].email).toBeFalsy() + }) + it('other roles than reviewer should see authors emails', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole({ fragment, role: 'author' }) + const { authors = [] } = result - expect(authors[0].email).toBeTruthy() - }) + expect(authors[0].email).toBeTruthy() + }) - it('stripeFragment - reviewer should not see cover letter', () => { - const { fragment } = testFixtures.fragments - const result = ah.stripeFragmentByRole({ fragment, role: 'reviewer' }) - const { files = {} } = result - expect(files.coverLetter).toBeFalsy() - }) - it('stripeFragment - reviewer should not see others reviews', () => { - const { fragment } = testFixtures.fragments - const result = ah.stripeFragmentByRole({ fragment, role: 'reviewer' }) - const { recommendations } = result - expect(recommendations).toEqual([]) - }) + it('reviewer should not see cover letter', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole({ fragment, role: 'reviewer' }) + const { files = {} } = result + expect(files.coverLetter).toBeFalsy() + }) + it('reviewer should not see others reviews', () => { + const { fragment } = testFixtures.fragments + const result = ah.stripeFragmentByRole({ fragment, role: 'reviewer' }) + const { recommendations } = result + expect(recommendations).toEqual([]) + }) - it('stripeFragment - author should not see recommendations if a decision has not been made', () => { - const { fragment } = testFixtures.fragments - fragment.recommendations = [ - { - comments: [ - { - content: 'private', - public: false, - }, - { - content: 'public', - public: true, - }, - ], - }, - ] - const { recommendations } = ah.stripeFragmentByRole({ - fragment, - role: 'author', - status: 'underReview', - isLast: true, - }) - expect(recommendations).toHaveLength(0) - }) - it('stripeFragment - author should see reviews only if recommendation has been made and only public ones', () => { - const { fragment } = testFixtures.fragments - fragment.recommendations = [ - { - comments: [ - { - content: 'private', - public: false, - }, - { - content: 'public', - public: true, - }, - ], - }, - ] - const result = ah.stripeFragmentByRole({ - fragment, - role: 'author', - status: 'revisionRequested', - }) - const publicComments = get(result, 'recommendations[0].comments') - expect(publicComments).toHaveLength(1) + it('author should not see recommendations if a decision has not been made', () => { + const { fragment } = testFixtures.fragments + fragment.recommendations = [ + { + comments: [ + { + content: 'private', + public: false, + }, + { + content: 'public', + public: true, + }, + ], + }, + ] + const { recommendations } = ah.stripeFragmentByRole({ + fragment, + role: 'author', + status: 'underReview', + isLast: true, + }) + expect(recommendations).toHaveLength(0) + }) + it('author should see reviews only if recommendation has been made and only public ones', () => { + const { fragment } = testFixtures.fragments + fragment.recommendations = [ + { + comments: [ + { + content: 'private', + public: false, + }, + { + content: 'public', + public: true, + }, + ], + }, + ] + const result = ah.stripeFragmentByRole({ + fragment, + role: 'author', + status: 'revisionRequested', + }) + const publicComments = get(result, 'recommendations[0].comments') + expect(publicComments).toHaveLength(1) + }) + it('HE should not see unsubmitted recommendations', () => { + const { fragment } = testFixtures.fragments + fragment.recommendations = [ + { + comments: [ + { + content: 'private', + public: false, + }, + { + content: 'public', + public: true, + }, + ], + }, + ] + const { recommendations } = ah.stripeFragmentByRole({ + fragment, + role: 'handlingEditor', + }) + expect(recommendations).toHaveLength(0) + }) + it('HE should see submitted recommendations', () => { + const { fragment } = testFixtures.fragments + fragment.recommendations = [ + { + comments: [ + { + content: 'private', + public: false, + }, + { + content: 'public', + public: true, + }, + ], + submittedOn: 1122333, + }, + ] + const { recommendations } = ah.stripeFragmentByRole({ + fragment, + role: 'handlingEditor', + }) + expect(recommendations).toHaveLength(1) + }) }) + it('getUsersList - should return parsed users when the user is admin', async () => { const { admin } = testFixtures.users const parsedUsers = await ah.getUsersList({