Skip to content
Snippets Groups Projects
Commit 0bac02ef authored by Anca Ursachi's avatar Anca Ursachi
Browse files

fix(mergeConflicts):

parents c12ab558 e3999201
No related branches found
No related tags found
2 merge requests!110Sprint 21 Features,!92Hin 946
Showing
with 492 additions and 93 deletions
Note: xpub is still _very_ new. This repository contains an initial set of components but is not yet ready for use.
## xPub-faraday ## xPub-faraday
...@@ -7,13 +6,11 @@ An MVP implementation of the first design sessions which allows a user to go thr ...@@ -7,13 +6,11 @@ An MVP implementation of the first design sessions which allows a user to go thr
## Roadmap ## Roadmap
The major tasks we're planning to work on are the following: The major tasks we're planning to work on are the following:
* Implement a future-proof theming setup. (#88) * Implement a future-proof theming setup.
* Let users go through multiple rounds of review. (#50) * Let users go through multiple rounds of review.
* Implement roles and permissions. (#58) * 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. (#67) * 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. (#55 #89 #57) * Merge xpub's authentication, routing and navigation with pubsweet's.
![Project roadmap](https://gitlab.coko.foundation/xpub/xpub-faraday/raw/master/packages/xpub-faraday/static/faraday-roadmap.png "Faraday Project Roadmap")
You can follow more fine-grained lists of things that we're working on 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 * [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 ...@@ -21,7 +18,7 @@ You can follow more fine-grained lists of things that we're working on
## Installing ## Installing
In the root directory, run `yarn` to install all the dependencies. In the root directory, run `yarn` to install all the dependencies.
## Configuration ## Configuration
Add the following values to `packages/xpub-collabra/config/local-development.json` 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 ...@@ -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 ## Running the app
1. `cd packages/xpub-faraday` 1. Open Docker engine
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). 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` 3. `yarn start`
......
...@@ -53,6 +53,7 @@ const cannotViewReviewersDetails = [ ...@@ -53,6 +53,7 @@ const cannotViewReviewersDetails = [
'submitted', 'submitted',
'heInvited', 'heInvited',
] ]
export const canViewReviewersDetails = (state, collection = {}) => { export const canViewReviewersDetails = (state, collection = {}) => {
if (cannotViewReviewersDetails.includes(get(collection, 'status', 'draft'))) { if (cannotViewReviewersDetails.includes(get(collection, 'status', 'draft'))) {
return false return false
...@@ -60,6 +61,42 @@ export const canViewReviewersDetails = (state, collection = {}) => { ...@@ -60,6 +61,42 @@ export const canViewReviewersDetails = (state, collection = {}) => {
return canViewReports(state, collection.id) 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 }) => export const getUserToken = ({ currentUser }) =>
get(currentUser, 'user.token', '') get(currentUser, 'user.token', '')
...@@ -254,6 +291,10 @@ export const getFragmentReviewerRecommendations = (state, fragmentId) => ...@@ -254,6 +291,10 @@ export const getFragmentReviewerRecommendations = (state, fragmentId) =>
getFragmentRecommendations(state, fragmentId).filter( getFragmentRecommendations(state, fragmentId).filter(
r => r.recommendationType === 'review', r => r.recommendationType === 'review',
) )
const getFragmentEditorialComments = (state, fragmentId) =>
getFragmentRecommendations(state, fragmentId).filter(
r => r.recommendationType === 'editorRecommendation',
)
const getOwnRecommendations = (state, fragmentId) => const getOwnRecommendations = (state, fragmentId) =>
chain(state) chain(state)
......
...@@ -9,26 +9,32 @@ import { Label, Item, Row, Text, Tag } from './' ...@@ -9,26 +9,32 @@ import { Label, Item, Row, Text, Tag } from './'
import { getReportComments } from './helpers' import { getReportComments } from './helpers'
const EditorialReportCard = ({ const EditorialReportCard = ({
publicLabel,
privateLabel,
journal, journal,
publicReport, publicReport,
privateReport, privateReport,
recommendation, recommendation,
reviewerName, editorName,
reviewerRole, editorRole,
report: { createdOn, reviewer }, report: { createdOn, reviewer },
}) => ( }) => (
<Root> <Root>
<Row justify="space-between" mb={2}> <Row justify="space-between" mb={2}>
<Item vertical> <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> <Text>{recommendation}</Text>
</Item> </Item>
<Item justify="flex-end"> <Item justify="flex-end">
{reviewer && ( {reviewer && (
<Fragment> <Fragment>
<Text mr={1 / 2}>{reviewerName}</Text> <Text mr={1 / 2}>{editorName}</Text>
<Tag mr={2}>{reviewerRole}</Tag> <Tag mr={2}>{editorRole}</Tag>
</Fragment> </Fragment>
)} )}
<DateParser timestamp={createdOn}> <DateParser timestamp={createdOn}>
...@@ -40,7 +46,7 @@ const EditorialReportCard = ({ ...@@ -40,7 +46,7 @@ const EditorialReportCard = ({
{publicReport && ( {publicReport && (
<Row mb={2}> <Row mb={2}>
<Item vertical> <Item vertical>
<Label mb={1 / 2}>Message For Author</Label> <Label mb={1 / 2}>{publicLabel}</Label>
<Text>{publicReport}</Text> <Text>{publicReport}</Text>
</Item> </Item>
</Row> </Row>
...@@ -49,7 +55,7 @@ const EditorialReportCard = ({ ...@@ -49,7 +55,7 @@ const EditorialReportCard = ({
{privateReport && ( {privateReport && (
<Row mb={2}> <Row mb={2}>
<Item vertical> <Item vertical>
<Label mb={1 / 2}>Message For Editorial Team</Label> <Label mb={1 / 2}>{privateLabel}</Label>
<Text>{privateReport}</Text> <Text>{privateReport}</Text>
</Item> </Item>
</Row> </Row>
...@@ -85,8 +91,8 @@ export default compose( ...@@ -85,8 +91,8 @@ export default compose(
recommendation: getRecommendationLabel(), recommendation: getRecommendationLabel(),
publicReport: getReportComments({ report, isPublic: true }), publicReport: getReportComments({ report, isPublic: true }),
privateReport: getReportComments({ report, isPublic: false }), privateReport: getReportComments({ report, isPublic: false }),
reviewerName: getReviewerName(), editorName: getReviewerName(),
reviewerRole: getReviewerRole(), editorRole: getReviewerRole(),
}), }),
), ),
)(EditorialReportCard) )(EditorialReportCard)
......
...@@ -47,7 +47,12 @@ const journal = { ...@@ -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 Card with message for the editorial team
...@@ -97,7 +102,12 @@ const journal = { ...@@ -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 Card with message for the editorial team and for the author
...@@ -152,5 +162,11 @@ const journal = { ...@@ -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"
/>
``` ```
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>&nbsp;</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
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')
}}/>
```
...@@ -83,7 +83,9 @@ const ReviewersTable = ({ ...@@ -83,7 +83,9 @@ const ReviewersTable = ({
</tbody> </tbody>
</Table> </Table>
) : ( ) : (
<Text align="center">No reviewers invited yet.</Text> <Text align="center" pb={2} pt={2}>
No reviewers invited yet.
</Text>
) )
const orderInvitations = i => { const orderInvitations = i => {
......
...@@ -12,6 +12,7 @@ import { ...@@ -12,6 +12,7 @@ import {
marginHelper, marginHelper,
ContextualBox, ContextualBox,
ReviewersTable, ReviewersTable,
PublonsTable,
ReviewerReport, ReviewerReport,
InviteReviewers, InviteReviewers,
ReviewerBreakdown, ReviewerBreakdown,
...@@ -24,10 +25,14 @@ const ReviewerDetails = ({ ...@@ -24,10 +25,14 @@ const ReviewerDetails = ({
reports = [], reports = [],
fragment, fragment,
invitations, invitations,
publonReviewers,
isFetching,
previewFile, previewFile,
downloadFile, downloadFile,
fetchingError,
canInviteReviewers, canInviteReviewers,
onInviteReviewer, onInviteReviewer,
onInvitePublonReviewer,
onResendReviewerInvite, onResendReviewerInvite,
onRevokeReviewerInvite, onRevokeReviewerInvite,
toggle, toggle,
...@@ -59,7 +64,16 @@ const ReviewerDetails = ({ ...@@ -59,7 +64,16 @@ const ReviewerDetails = ({
> >
<H4>Reviewer Details</H4> <H4>Reviewer Details</H4>
</TabButton> </TabButton>
{canInviteReviewers && (
<TabButton
ml={1}
mr={1}
onClick={() => changeTab(2)}
selected={selectedTab === 2}
>
<H4>Reviewer Suggestions</H4>
</TabButton>
)}
<TabButton <TabButton
ml={1} ml={1}
mr={1} mr={1}
...@@ -86,6 +100,14 @@ const ReviewerDetails = ({ ...@@ -86,6 +100,14 @@ const ReviewerDetails = ({
/> />
</Fragment> </Fragment>
)} )}
{selectedTab === 2 && (
<PublonsTable
onInvite={onInvitePublonReviewer}
publonsError={fetchingError}
publonsFetching={isFetching}
reviewers={publonReviewers}
/>
)}
{selectedTab === 1 && ( {selectedTab === 1 && (
<Fragment> <Fragment>
{reports.length === 0 && ( {reports.length === 0 && (
...@@ -114,14 +136,22 @@ const ReviewerDetails = ({ ...@@ -114,14 +136,22 @@ const ReviewerDetails = ({
export default compose( export default compose(
withFilePreview, withFilePreview,
withFileDownload, withFileDownload,
withProps(({ invitations = [], reviewerReports = [], currentUser }) => ({ withProps(
token: get(currentUser, 'token', ''), ({
invitations: invitations.map(i => ({ invitations = [],
...i, publonReviewers = [],
review: reviewerReports.find(r => r.userId === i.userId), reviewerReports = [],
})), currentUser,
reports: reviewerReports.filter(r => r.submittedOn), }) => ({
})), 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 }) => ({ withProps(({ currentUser }) => ({
canInviteReviewers: get(currentUser, 'permissions.canInviteReviewers'), canInviteReviewers: get(currentUser, 'permissions.canInviteReviewers'),
canViewReviewersDetails: get( canViewReviewersDetails: get(
......
...@@ -30,6 +30,7 @@ export { default as Pagination } from './Pagination' ...@@ -30,6 +30,7 @@ export { default as Pagination } from './Pagination'
export { default as PersonInfo } from './PersonInfo' export { default as PersonInfo } from './PersonInfo'
export { default as PersonInvitation } from './PersonInvitation' export { default as PersonInvitation } from './PersonInvitation'
export { default as PreviewFile } from './PreviewFile' export { default as PreviewFile } from './PreviewFile'
export { default as PublonsTable } from './PublonsTable'
export { default as RadioWithComments } from './RadioWithComments' export { default as RadioWithComments } from './RadioWithComments'
export { default as ReviewerReport } from './ReviewerReport' export { default as ReviewerReport } from './ReviewerReport'
export { default as ReviewersTable } from './ReviewersTable' export { default as ReviewersTable } from './ReviewersTable'
......
...@@ -27,7 +27,7 @@ const MultiAction = ({ ...@@ -27,7 +27,7 @@ const MultiAction = ({
)} )}
{renderContent()} {renderContent()}
{modalError && ( {modalError && (
<Text error mt={1}> <Text align="center" error mt={1}>
{modalError} {modalError}
</Text> </Text>
)} )}
...@@ -83,7 +83,7 @@ const Root = styled.div` ...@@ -83,7 +83,7 @@ const Root = styled.div`
flex-direction: column; flex-direction: column;
position: relative; position: relative;
padding: calc(${th('gridUnit')} * 5); padding: calc(${th('gridUnit')} * 5);
width: calc(${th('gridUnit')} * 60); width: calc(${th('gridUnit')} * 70);
${H2} { ${H2} {
margin: 0 0 ${th('gridUnit')} 0; margin: 0 0 ${th('gridUnit')} 0;
......
...@@ -16,7 +16,12 @@ const FragmentsInvitations = app => { ...@@ -16,7 +16,12 @@ const FragmentsInvitations = app => {
* @apiParamExample {json} Body * @apiParamExample {json} Body
* { * {
* "email": "email@example.com", * "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 * @apiSuccessExample {json} Success
* HTTP/1.1 200 OK * HTTP/1.1 200 OK
...@@ -51,8 +56,8 @@ const FragmentsInvitations = app => { ...@@ -51,8 +56,8 @@ const FragmentsInvitations = app => {
* HTTP/1.1 200 OK * HTTP/1.1 200 OK
* [{ * [{
* "name": "John Smith", * "name": "John Smith",
* "invitedOn": 1525428890167, * "invitedOn": 1525428890167,
* "respondedOn": 1525428890299, * "respondedOn": 1525428890299,
* "email": "email@example.com", * "email": "email@example.com",
* "status": "pending", * "status": "pending",
* "invitationId": "1990881" * "invitationId": "1990881"
......
const logger = require('@pubsweet/logger') const logger = require('@pubsweet/logger')
const { const {
Team, Team,
User, User,
...@@ -122,10 +123,33 @@ module.exports = models => async (req, res) => { ...@@ -122,10 +123,33 @@ module.exports = models => async (req, res) => {
} catch (e) { } catch (e) {
const userHelper = new User({ UserModel }) const userHelper = new User({ UserModel })
const newUser = await userHelper.createUser({ const userData = req.body
role, const { firstName = '', lastName = '', isPublons } = userData
body: req.body, 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') if (collection.status === 'heAssigned')
await collectionHelper.updateStatus({ newStatus: 'reviewersInvited' }) await collectionHelper.updateStatus({ newStatus: 'reviewersInvited' })
......
...@@ -15,7 +15,13 @@ const EditorialCommentCard = ({ journal, reports = [], toggle, expanded }) => ( ...@@ -15,7 +15,13 @@ const EditorialCommentCard = ({ journal, reports = [], toggle, expanded }) => (
toggle={toggle} toggle={toggle}
> >
{reports.map(report => ( {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> </ContextualBox>
) )
......
...@@ -38,6 +38,7 @@ const ManuscriptLayout = ({ ...@@ -38,6 +38,7 @@ const ManuscriptLayout = ({
fragment = {}, fragment = {},
changeForm, changeForm,
isFetching, isFetching,
fetchingError,
formValues, formValues,
heExpanded, heExpanded,
onHEResponse, onHEResponse,
...@@ -50,13 +51,13 @@ const ManuscriptLayout = ({ ...@@ -50,13 +51,13 @@ const ManuscriptLayout = ({
onRevokeReviewerInvite, onRevokeReviewerInvite,
toggleReviewerResponse, toggleReviewerResponse,
invitationsWithReviewers, invitationsWithReviewers,
publonReviewers,
reviewerResponseExpanded, reviewerResponseExpanded,
pendingOwnRecommendation, pendingOwnRecommendation,
toggleReviewerRecommendations, toggleReviewerRecommendations,
reviewerRecommendationExpanded, reviewerRecommendationExpanded,
shouldReview, shouldReview,
submittedOwnRecommendation, submittedOwnRecommendation,
heAccepted,
reviewerReports, reviewerReports,
onEditorialRecommendation, onEditorialRecommendation,
reviewerRecommendations, reviewerRecommendations,
...@@ -64,6 +65,7 @@ const ManuscriptLayout = ({ ...@@ -64,6 +65,7 @@ const ManuscriptLayout = ({
reviewerDetailsExpanded, reviewerDetailsExpanded,
toggleHeRecommendation, toggleHeRecommendation,
heRecommendationExpanded, heRecommendationExpanded,
onInvitePublonReviewer,
}) => ( }) => (
<Root pb={30}> <Root pb={30}>
{!isEmpty(collection) && !isEmpty(fragment) ? ( {!isEmpty(collection) && !isEmpty(fragment) ? (
...@@ -95,15 +97,14 @@ const ManuscriptLayout = ({ ...@@ -95,15 +97,14 @@ const ManuscriptLayout = ({
getSignedUrl={getSignedUrl} getSignedUrl={getSignedUrl}
/> />
{get(currentUser, 'permissions.canViewReports', true) && {get(currentUser, 'permissions.canViewEditorialComments', true) && (
!!editorialRecommendations.length && ( <EditorialCommentCard
<EditorialCommentCard expanded={heRecommendationExpanded}
expanded={heRecommendationExpanded} journal={journal}
journal={journal} reports={editorialRecommendations}
reports={editorialRecommendations} toggle={toggleHeRecommendation}
toggle={toggleHeRecommendation} />
/> )}
)}
{submittedOwnRecommendation && ( {submittedOwnRecommendation && (
<ReviewerReportCard <ReviewerReportCard
...@@ -163,15 +164,19 @@ const ManuscriptLayout = ({ ...@@ -163,15 +164,19 @@ const ManuscriptLayout = ({
<ReviewerDetails <ReviewerDetails
currentUser={currentUser} currentUser={currentUser}
expanded={reviewerDetailsExpanded} expanded={reviewerDetailsExpanded}
fetchingError={fetchingError}
fragment={fragment} fragment={fragment}
getSignedUrl={getSignedUrl} getSignedUrl={getSignedUrl}
highlight={reviewerReports.length === 0} highlight={reviewerReports.length === 0}
invitations={invitationsWithReviewers} invitations={invitationsWithReviewers}
isFetching={isFetching.publonsFetching}
journal={journal} journal={journal}
mb={2} mb={2}
onInvitePublonReviewer={onInvitePublonReviewer}
onInviteReviewer={onInviteReviewer} onInviteReviewer={onInviteReviewer}
onResendReviewerInvite={onResendReviewerInvite} onResendReviewerInvite={onResendReviewerInvite}
onRevokeReviewerInvite={onRevokeReviewerInvite} onRevokeReviewerInvite={onRevokeReviewerInvite}
publonReviewers={publonReviewers}
reviewerReports={reviewerReports} reviewerReports={reviewerReports}
scrollIntoView scrollIntoView
toggle={toggleReviewerDetails} toggle={toggleReviewerDetails}
......
...@@ -48,6 +48,7 @@ import { ...@@ -48,6 +48,7 @@ import {
currentUserIsReviewer, currentUserIsReviewer,
parseCollectionDetails, parseCollectionDetails,
canMakeHERecommendation, canMakeHERecommendation,
canViewEditorialComments,
canViewReviewersDetails, canViewReviewersDetails,
pendingReviewerInvitation, pendingReviewerInvitation,
canOverrideTechnicalChecks, canOverrideTechnicalChecks,
...@@ -56,10 +57,19 @@ import { ...@@ -56,10 +57,19 @@ import {
getFragmentReviewerRecommendations, getFragmentReviewerRecommendations,
getInvitationsWithReviewersForFragment, getInvitationsWithReviewersForFragment,
} from 'pubsweet-component-faraday-selectors' } 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 ManuscriptLayout from './ManuscriptLayout'
import { parseEicDecision, parseSearchParams, redirectToError } from './utils' import {
parseEicDecision,
parseSearchParams,
redirectToError,
getPublonsReviewers,
} from './utils'
import { import {
canAssignHE, canAssignHE,
selectFetching, selectFetching,
...@@ -74,6 +84,8 @@ export default compose( ...@@ -74,6 +84,8 @@ export default compose(
setDisplayName('ManuscriptPage'), setDisplayName('ManuscriptPage'),
withJournal, withJournal,
withRouter, withRouter,
withFetching,
withState('publonReviewers', 'setPublonsReviewers', []),
withState('editorInChief', 'setEiC', 'N/A'), withState('editorInChief', 'setEiC', 'N/A'),
ConnectPage(({ match }) => [ ConnectPage(({ match }) => [
actions.getCollection({ id: match.params.project }), actions.getCollection({ id: match.params.project }),
...@@ -136,6 +148,7 @@ export default compose( ...@@ -136,6 +148,7 @@ export default compose(
pendingHEInvitation, pendingHEInvitation,
pendingOwnRecommendation, pendingOwnRecommendation,
pendingReviewerInvitation, pendingReviewerInvitation,
isFetching,
}, },
) => ({ ) => ({
currentUser: { currentUser: {
...@@ -154,6 +167,11 @@ export default compose( ...@@ -154,6 +167,11 @@ export default compose(
}), }),
canAssignHE: canAssignHE(state, match.params.project), canAssignHE: canAssignHE(state, match.params.project),
canViewReports: canViewReports(state, match.params.project), canViewReports: canViewReports(state, match.params.project),
canViewEditorialComments: canViewEditorialComments(
state,
collection,
match.params.version,
),
canInviteReviewers: canInviteReviewers(state, collection), canInviteReviewers: canInviteReviewers(state, collection),
canMakeRecommendation: !isUndefined(pendingOwnRecommendation), canMakeRecommendation: !isUndefined(pendingOwnRecommendation),
canMakeRevision: canMakeRevision(state, collection, fragment), canMakeRevision: canMakeRevision(state, collection, fragment),
...@@ -165,6 +183,7 @@ export default compose( ...@@ -165,6 +183,7 @@ export default compose(
}, },
isFetching: { isFetching: {
editorsFetching: selectFetching(state), editorsFetching: selectFetching(state),
publonsFetching: isFetching,
}, },
formValues: { formValues: {
eicDecision: getFormValues('eic-decision')(state), eicDecision: getFormValues('eic-decision')(state),
...@@ -196,6 +215,22 @@ export default compose( ...@@ -196,6 +215,22 @@ export default compose(
getFragment(collection, fragment) getFragment(collection, fragment)
getUsers() 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({ withHandlers({
updateManuscript: ({ updateVersion, collection, fragment }) => data => updateManuscript: ({ updateVersion, collection, fragment }) => data =>
...@@ -403,6 +438,35 @@ export default compose( ...@@ -403,6 +438,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 }) => ({ fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
toggleAssignHE: toggle, toggleAssignHE: toggle,
heExpanded: expanded, heExpanded: expanded,
...@@ -438,11 +502,13 @@ export default compose( ...@@ -438,11 +502,13 @@ export default compose(
const { const {
match, match,
history, history,
setError,
location, location,
shouldReview, shouldReview,
reviewerReports, reviewerReports,
setEditorInChief, setEditorInChief,
clearCustomError, clearCustomError,
getPublonsReviewers,
hasManuscriptFailure, hasManuscriptFailure,
fetchUpdatedCollection, fetchUpdatedCollection,
editorialRecommendations, editorialRecommendations,
...@@ -451,8 +517,10 @@ export default compose( ...@@ -451,8 +517,10 @@ export default compose(
isInvitedToReview, isInvitedToReview,
isHEToManuscript, isHEToManuscript,
isEIC, isEIC,
permissions: { canInviteReviewers },
}, },
} = this.props } = this.props
if (hasManuscriptFailure) { if (hasManuscriptFailure) {
history.push('/not-found') history.push('/not-found')
clearCustomError() clearCustomError()
...@@ -472,6 +540,10 @@ export default compose( ...@@ -472,6 +540,10 @@ export default compose(
setEditorInChief(head(res.users)), setEditorInChief(head(res.users)),
) )
if (canInviteReviewers) {
getPublonsReviewers(fragmentId, setError)
}
if (isInvitedHE) { if (isInvitedHE) {
this.props.toggleHEResponse() this.props.toggleHEResponse()
} }
......
...@@ -13,6 +13,7 @@ import { change as changeForm } from 'redux-form' ...@@ -13,6 +13,7 @@ import { change as changeForm } from 'redux-form'
import { actions } from 'pubsweet-client/src' import { actions } from 'pubsweet-client/src'
import { handleError } from 'pubsweet-component-faraday-ui' import { handleError } from 'pubsweet-component-faraday-ui'
import { get as apiGet } from 'pubsweet-client/src/helpers/api'
import { import {
autosaveRequest, autosaveRequest,
...@@ -210,6 +211,7 @@ export const onReviewSubmit = ( ...@@ -210,6 +211,7 @@ export const onReviewSubmit = (
collectionId: project.id, collectionId: project.id,
recommendation: newValues, recommendation: newValues,
}) })
.then(() => dispatch(actions.getCollection({ id: project.id })))
.then(() => { .then(() => {
dispatch(actions.getFragments({ id: project.id })) dispatch(actions.getFragments({ id: project.id }))
hideModal() hideModal()
...@@ -275,7 +277,6 @@ export const onRevisionSubmit = ( ...@@ -275,7 +277,6 @@ export const onRevisionSubmit = (
onCancel: hideModal, onCancel: hideModal,
}) })
} }
// revision validators // revision validators
export const requiredHTML = value => { export const requiredHTML = value => {
if (value && value.replace('<p></p>', '').replace('<h1></h1>', '')) { if (value && value.replace('<p></p>', '').replace('<h1></h1>', '')) {
...@@ -303,3 +304,7 @@ export const parseEicDecision = ({ decision, message }) => ({ ...@@ -303,3 +304,7 @@ export const parseEicDecision = ({ decision, message }) => ({
}, },
], ],
}) })
// handle publons
export const getPublonsReviewers = fragmentId =>
apiGet(`/fragments/${fragmentId}/publons`)
...@@ -40,7 +40,8 @@ module.exports = models => async (req, res) => { ...@@ -40,7 +40,8 @@ module.exports = models => async (req, res) => {
fragmentId: fragment.id, fragmentId: fragment.id,
}) })
const parsedFragment = await fragmentHelper.getFragmentData() const parsedFragment = await fragmentHelper.getFragmentData()
const memberIds = await teamHelper.getTeamMembers({ let memberIds = []
memberIds = await teamHelper.getTeamMembers({
role: 'reviewer', role: 'reviewer',
objectType: 'fragment', objectType: 'fragment',
}) })
......
...@@ -3,23 +3,13 @@ import { ActionLink, Text, Row } from 'pubsweet-component-faraday-ui' ...@@ -3,23 +3,13 @@ import { ActionLink, Text, Row } from 'pubsweet-component-faraday-ui'
const SubmissionStatement = () => ( const SubmissionStatement = () => (
<Fragment> <Fragment>
<Row mb={1}> <Row mb={1} mt={2}>
<Text> <Text>
This manuscript is not currently submitted to or under consideration in This manuscript is not currently submitted to or under consideration in
any other journals. any other journals.
</Text> </Text>
</Row> </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}> <Row mb={1}>
<Text> <Text>
The manuscript complies with all relevant{' '} The manuscript complies with all relevant{' '}
...@@ -29,28 +19,13 @@ const SubmissionStatement = () => ( ...@@ -29,28 +19,13 @@ const SubmissionStatement = () => (
</Text> </Text>
</Row> </Row>
<Row mb={1}> <Row mb={3}>
<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}>
<Text> <Text>
You have read and understood the{' '} You have read and understood the{' '}
<ActionLink to="https://www.hindawi.com/terms/"> <ActionLink to="https://www.hindawi.com/terms/">
terms of service terms of service
</ActionLink> </ActionLink>{' '}
{' & '} and{' '}
<ActionLink to="https://www.hindawi.com/privacy/"> <ActionLink to="https://www.hindawi.com/privacy/">
privacy policy privacy policy
</ActionLink>{' '} </ActionLink>{' '}
......
...@@ -4,6 +4,7 @@ import { reduxForm } from 'redux-form' ...@@ -4,6 +4,7 @@ import { reduxForm } from 'redux-form'
import { get, isUndefined } from 'lodash' import { get, isUndefined } from 'lodash'
import { required as requiredValidator } from 'xpub-validators' import { required as requiredValidator } from 'xpub-validators'
import { Menu, Button, Checkbox, TextField, ValidatedField } from '@pubsweet/ui' import { Menu, Button, Checkbox, TextField, ValidatedField } from '@pubsweet/ui'
import styled from 'styled-components'
import { import {
Row, Row,
...@@ -16,7 +17,7 @@ import { ...@@ -16,7 +17,7 @@ import {
} from 'pubsweet-component-faraday-ui' } from 'pubsweet-component-faraday-ui'
const AgreeCheckbox = ({ value, onChange }) => ( 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} /> <Checkbox checked={value} onChange={onChange} value={value} />
<Text> <Text>
I agree with the{' '} I agree with the{' '}
...@@ -26,6 +27,9 @@ const AgreeCheckbox = ({ value, onChange }) => ( ...@@ -26,6 +27,9 @@ const AgreeCheckbox = ({ value, onChange }) => (
</Text> </Text>
</Row> </Row>
) )
const ValidationRow = styled(Row)`
justify-content: left;
`
const Step0 = ({ const Step0 = ({
type, type,
...@@ -98,11 +102,13 @@ const Step0 = ({ ...@@ -98,11 +102,13 @@ const Step0 = ({
</Item> </Item>
</Row> </Row>
<ValidatedField <ValidationRow mb={2}>
component={AgreeCheckbox} <ValidatedField
name="agreeTC" component={AgreeCheckbox}
validate={[requiredValidator]} name="agreeTC"
/> validate={[requiredValidator]}
/>
</ValidationRow>
<Row> <Row>
<Text secondary small> <Text secondary small>
......
...@@ -59,10 +59,16 @@ export const getCollectionReviewers = (collectionId, fragmentId) => dispatch => ...@@ -59,10 +59,16 @@ export const getCollectionReviewers = (collectionId, fragmentId) => dispatch =>
) )
// #endregion // #endregion
export const inviteReviewer = ({ reviewerData, collectionId, fragmentId }) => export const inviteReviewer = ({
fragmentId,
reviewerData,
collectionId,
isPublons = false,
}) =>
create(`/collections/${collectionId}/fragments/${fragmentId}/invitations`, { create(`/collections/${collectionId}/fragments/${fragmentId}/invitations`, {
...reviewerData, ...reviewerData,
role: 'reviewer', role: 'reviewer',
isPublons,
}) })
// #region Actions - invitations // #region Actions - invitations
......
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