Skip to content
Snippets Groups Projects
Commit 7fe80e76 authored by Daniel Sandu's avatar Daniel Sandu
Browse files

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

parents acf3965a 6e2d66fc
No related branches found
No related tags found
3 merge requests!196S25 - EiC submit revision,!189S25,!175Hin 1138 filter
Showing
with 348 additions and 75 deletions
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<td width="300.000px" valign="top" style="padding: 0px 0px 0px 0px;border-collapse: collapse;" > <td width="300.000px" valign="top" style="padding: 0px 0px 0px 0px;border-collapse: collapse;" >
<![endif]--> <![endif]-->
<table width="300.000" style="width:300.000px;border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;" <table width="300.000" style="width:'50%';border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;"
cellpadding="0" cellspacing="0" align="left" border="0" bgcolor="" class="column column-0 of-2 cellpadding="0" cellspacing="0" align="left" border="0" bgcolor="" class="column column-0 of-2
empty"> empty">
<tr> <tr>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
role="module" style="table-layout:fixed" width="100%"> role="module" style="table-layout:fixed" width="100%">
<tbody> <tbody>
<tr> <tr>
<td align="center" class="outer-td" style="padding:0px 0px 0px 50px"> <td align="center" class="outer-td padding-decline">
<table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile" <table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile"
style="text-align:center"> style="text-align:center">
<tbody> <tbody>
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
<td width="300.000px" valign="top" style="padding: 0px 0px 0px 0px;border-collapse: collapse;" > <td width="300.000px" valign="top" style="padding: 0px 0px 0px 0px;border-collapse: collapse;" >
<![endif]--> <![endif]-->
<table width="300.000" style="width:300.000px;border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;" <table width="300.000" style="width:'50%';border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;"
cellpadding="0" cellspacing="0" align="left" border="0" bgcolor="" class="column column-1 of-2 cellpadding="0" cellspacing="0" align="left" border="0" bgcolor="" class="column column-1 of-2
empty"> empty">
<tr> <tr>
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
role="module" style="table-layout:fixed" width="100%"> role="module" style="table-layout:fixed" width="100%">
<tbody> <tbody>
<tr> <tr>
<td align="center" class="outer-td" style="padding:0px 50px 0px 0px"> <td align="center" class="outer-td padding-agree">
<table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile" <table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile"
style="text-align:center"> style="text-align:center">
<tbody> <tbody>
......
...@@ -76,6 +76,14 @@ ...@@ -76,6 +76,14 @@
text-decoration: none; text-decoration: none;
} }
.padding-decline {
padding:0px 0px 0px 50px;
}
.padding-agree {
padding:0px 50px 0px 0px;
}
@media screen and (max-width:480px) { @media screen and (max-width:480px) {
.preheader .rightColumnContent, .preheader .rightColumnContent,
...@@ -126,6 +134,14 @@ ...@@ -126,6 +134,14 @@
margin-left: 0 !important; margin-left: 0 !important;
margin-right: 0 !important; margin-right: 0 !important;
} }
.padding-decline {
padding: 0px;
}
.padding-agree {
padding: 10px 0px 0px 0px;
}
} }
</style> </style>
<!--user entered Head Start--> <!--user entered Head Start-->
......
import { selectCurrentUser } from 'xpub-selectors' import { selectCurrentUser } from 'xpub-selectors'
// eslint-disable-next-line no-unused-vars import { get, has, last, chain, some, isEmpty, slice, find } from 'lodash'
import { get, has, last, chain, some, isEmpty, flatten } from 'lodash'
export const isHEToManuscript = (state, collectionId = '') => { export const isHEToManuscript = (state, collectionId = '') => {
const { id = '', isAccepted = false } = chain(state) const { id = '', isAccepted = false } = chain(state)
...@@ -49,6 +48,29 @@ export const canInviteReviewers = (state, collection = {}) => { ...@@ -49,6 +48,29 @@ export const canInviteReviewers = (state, collection = {}) => {
return isAccepted && (userId === heId || isAdminEiC) return isAccepted && (userId === heId || isAdminEiC)
} }
const canViewContextualBoxOnOldVersionStatuses = [
'submitted',
'heInvited',
'heAssigned',
]
const canViewContextualBoxOnOldVersion = (collection, fragmentId) => {
const fragments = get(collection, 'fragments', [])
const oldVersions = slice(fragments, 0, fragments.length - 1)
const isOldVersion = !!find(oldVersions, fragment => fragment === fragmentId)
return (
isOldVersion &&
canViewContextualBoxOnOldVersionStatuses.includes(
get(collection, 'status', 'draft'),
)
)
}
const canHEViewContextualBoxOnOldVersion = (collection, fragmentId) => {
const fragments = get(collection, 'fragments', [])
const oldVersions = slice(fragments, 0, fragments.length - 1)
const isOldVersion = !!find(oldVersions, fragment => fragment === fragmentId)
return isOldVersion && get(collection, 'status', 'draft') === 'heInvited'
}
const cannotViewReviewersDetails = [ const cannotViewReviewersDetails = [
'draft', 'draft',
'technicalChecks', 'technicalChecks',
...@@ -67,12 +89,14 @@ export const canViewReviewersDetails = (state, collection = {}) => { ...@@ -67,12 +89,14 @@ export const canViewReviewersDetails = (state, collection = {}) => {
return canViewReports(state, get(collection, 'id', '')) return canViewReports(state, get(collection, 'id', ''))
} }
const authorCanViewReportsDetailsStatuses = [ const authorAndReviewersCanViewReportsDetailsStatuses = [
'revisionRequested', 'revisionRequested',
'underReview',
'pendingApproval', 'pendingApproval',
'rejected', 'rejected',
'accepted', 'accepted',
'reviewCompleted', 'reviewCompleted',
'reviewersInvited',
'inQa', 'inQa',
] ]
...@@ -83,9 +107,27 @@ export const authorCanViewReportsDetails = ( ...@@ -83,9 +107,27 @@ export const authorCanViewReportsDetails = (
) => { ) => {
const isAuthor = currentUserIsAuthor(state, fragmentId) const isAuthor = currentUserIsAuthor(state, fragmentId)
return ( return (
authorCanViewReportsDetailsStatuses.includes( isAuthor &&
(authorAndReviewersCanViewReportsDetailsStatuses.includes(
get(collection, 'status', 'draft'), get(collection, 'status', 'draft'),
) && isAuthor ) ||
canViewContextualBoxOnOldVersion(collection, fragmentId))
)
}
export const reviewersCanViewReviewerReports = (
state,
collection = {},
fragmentId,
) => {
const isReviewer = currentUserIsReviewer(state, fragmentId)
const reviewerReports = getFragmentReviewerRecommendations(state, fragmentId)
return (
isReviewer &&
authorAndReviewersCanViewReportsDetailsStatuses.includes(
get(collection, 'status', 'draft'),
) &&
reviewerReports.length > 0
) )
} }
...@@ -125,6 +167,7 @@ const canReviewerViewEditorialCommentsStatuses = [ ...@@ -125,6 +167,7 @@ const canReviewerViewEditorialCommentsStatuses = [
'reviewCompleted', 'reviewCompleted',
'pendingApproval', 'pendingApproval',
'revisionRequested', 'revisionRequested',
'reviewersInvited',
] ]
export const canReviewerViewEditorialComments = ( export const canReviewerViewEditorialComments = (
state, state,
...@@ -176,8 +219,13 @@ export const canViewEditorialComments = ( ...@@ -176,8 +219,13 @@ export const canViewEditorialComments = (
state, state,
fragmentId, fragmentId,
) )
const isHE = currentUserIs(state, 'isHE')
const canViewEditorialCommentsOnOldVersion = isHE
? !canHEViewContextualBoxOnOldVersion(collection, fragmentId)
: canViewContextualBoxOnOldVersion(collection, fragmentId)
return ( return (
(canHeViewEditorialComments(state, collection) || (canViewEditorialCommentsOnOldVersion ||
canHeViewEditorialComments(state, collection) ||
canEICViewEditorialComments(state, collection) || canEICViewEditorialComments(state, collection) ||
canReviewerViewEditorialComments(state, collection, fragment) || canReviewerViewEditorialComments(state, collection, fragment) ||
canAuthorViewEditorialComments(state, collection, fragmentId)) && canAuthorViewEditorialComments(state, collection, fragmentId)) &&
...@@ -185,17 +233,22 @@ export const canViewEditorialComments = ( ...@@ -185,17 +233,22 @@ export const canViewEditorialComments = (
) )
} }
const cannotViewResponseFromAuthorStatuses = ['reviewersInvited']
export const canViewResponseFromAuthor = (state, collection, fragmentId) => { export const canViewResponseFromAuthor = (state, collection, fragmentId) => {
const authorResponseToRevisonRequest = getFragmentAuthorResponse( const authorResponseToRevisonRequest = getFragmentAuthorResponse(
state, state,
fragmentId, fragmentId,
) )
const canHEViewResponseFromAuthor =
currentUserIs(state, 'isHE') &&
get(collection, 'status', 'draft') === 'heInvited'
const canReviewerViewResponsefromAuthor =
currentUserIsReviewerInPending(state, fragmentId) &&
get(collection, 'status', 'draft') === 'reviewersInvited'
return ( return (
!isEmpty(authorResponseToRevisonRequest) && !isEmpty(authorResponseToRevisonRequest) &&
!cannotViewResponseFromAuthorStatuses.includes( !canHEViewResponseFromAuthor &&
get(collection, 'status', 'draft'), !canReviewerViewResponsefromAuthor
)
) )
} }
...@@ -326,6 +379,13 @@ export const pendingReviewerInvitation = (state, fragmentId) => ...@@ -326,6 +379,13 @@ export const pendingReviewerInvitation = (state, fragmentId) =>
) )
.value() .value()
export const currentUserIsReviewerInPending = (state, fragmentId) => {
const currentUser = selectCurrentUser(state)
const invitations = get(state, `fragments.${fragmentId}.invitations`, [])
return !!invitations.find(
i => i.userId === currentUser.id && i.role === 'reviewer' && !i.isAccepted,
)
}
export const currentUserIsReviewer = (state, fragmentId) => { export const currentUserIsReviewer = (state, fragmentId) => {
const currentUser = selectCurrentUser(state) const currentUser = selectCurrentUser(state)
const invitations = get(state, `fragments.${fragmentId}.invitations`, []) const invitations = get(state, `fragments.${fragmentId}.invitations`, [])
...@@ -496,11 +556,12 @@ export const getVersionOptions = (state, collection = {}) => { ...@@ -496,11 +556,12 @@ export const getVersionOptions = (state, collection = {}) => {
export const canReview = (state, collection = {}, fragment = {}) => { export const canReview = (state, collection = {}, fragment = {}) => {
const fragmentId = get(fragment, 'id', false) const fragmentId = get(fragment, 'id', false)
if (!fragmentId) return false if (!fragmentId) return false
const ownRecommendation = getOwnRecommendations(state, fragmentId)
const isReviewer = currentUserIsReviewer(state, fragmentId) const isReviewer = currentUserIsReviewer(state, fragmentId)
if (!isReviewer) return false if (!isReviewer) return false
return (
return get(collection, 'status', 'draft') === 'underReview' get(collection, 'status', 'draft') === 'underReview' &&
ownRecommendation.length === 0
)
} }
...@@ -76,7 +76,7 @@ const ManuscriptCard = ({ ...@@ -76,7 +76,7 @@ const ManuscriptCard = ({
<Row alignItems="center" justify="flex-start" mb={1}> <Row alignItems="center" justify="flex-start" mb={1}>
<H4>Handling editor</H4> <H4>Handling editor</H4>
<Text ml={1} mr={3} whiteSpace="nowrap"> <Text ml={1} mr={3} whiteSpace="nowrap">
{get(handlingEditor, 'name', 'Undefined')} {get(handlingEditor, 'name', 'Unassigned')}
</Text> </Text>
{canViewReports && ( {canViewReports && (
<Fragment> <Fragment>
......
...@@ -9,6 +9,7 @@ const PersonInvitation = ({ ...@@ -9,6 +9,7 @@ const PersonInvitation = ({
withName, withName,
hasAnswer, hasAnswer,
isFetching, isFetching,
isLatestVersion,
revokeInvitation, revokeInvitation,
resendInvitation, resendInvitation,
person: { name, email }, person: { name, email },
...@@ -57,6 +58,29 @@ const PersonInvitation = ({ ...@@ -57,6 +58,29 @@ const PersonInvitation = ({
</OpenModal> </OpenModal>
</Fragment> </Fragment>
)} )}
{hasAnswer &&
isLatestVersion && (
<Fragment>
<OpenModal
confirmText="Revoke"
isFetching={isFetching}
modalKey={`remove-${id}`}
onConfirm={revokeInvitation}
subtitle="Deleting the handling editor at this moment will also remove all his work."
title="Revoke invitation?"
>
{showModal => (
<IconButton
icon="x-circle"
iconSize={2}
ml={2}
onClick={showModal}
secondary
/>
)}
</OpenModal>
</Fragment>
)}
</Root> </Root>
) )
......
...@@ -11,10 +11,11 @@ const ReviewerReport = ({ ...@@ -11,10 +11,11 @@ const ReviewerReport = ({
onPreview, onPreview,
onDownload, onDownload,
reportFile, reportFile,
currentUser,
publicReport, publicReport,
privateReport, privateReport,
reviewerName, reviewerName,
reviewerIndex, reviewerNumber,
recommendation, recommendation,
showOwner = false, showOwner = false,
report: { submittedOn }, report: { submittedOn },
...@@ -27,14 +28,12 @@ const ReviewerReport = ({ ...@@ -27,14 +28,12 @@ const ReviewerReport = ({
</Item> </Item>
<Item justify="flex-end"> <Item justify="flex-end">
{showOwner && ( <Fragment>
<Fragment> {showOwner && <Text>{reviewerName}</Text>}
<Text>{reviewerName}</Text> <Text customId ml={1} mr={1}>
<Text customId ml={1} mr={1}> {`Reviewer ${reviewerNumber}`}
{`Reviewer ${reviewerIndex}`} </Text>
</Text> </Fragment>
</Fragment>
)}
<DateParser timestamp={submittedOn}> <DateParser timestamp={submittedOn}>
{date => <Text>{date}</Text>} {date => <Text>{date}</Text>}
</DateParser> </DateParser>
...@@ -76,20 +75,22 @@ const ReviewerReport = ({ ...@@ -76,20 +75,22 @@ const ReviewerReport = ({
</Root> </Root>
) )
export default withProps(({ report, journal: { recommendations = [] } }) => ({ export default withProps(
recommendation: get( ({ report, currentUser, journal: { recommendations = [] } }) => ({
recommendations.find(r => r.value === report.recommendation), recommendation: get(
'label', recommendations.find(r => r.value === report.recommendation),
), 'label',
reportFile: get(report, 'comments.0.files.0'), ),
publicReport: get(report, 'comments.0.content'), reportFile: get(report, 'comments.0.files.0'),
privateReport: get(report, 'comments.1.content'), publicReport: get(report, 'comments.0.content'),
reviewerName: `${get(report, 'reviewer.firstName', '')} ${get( privateReport: get(report, 'comments.1.content'),
report, reviewerName: `${get(currentUser, 'firstName', '')} ${get(
'reviewer.lastName', currentUser,
'', 'lastName',
)}`, '',
}))(ReviewerReport) )}`,
}),
)(ReviewerReport)
// #region styles // #region styles
const Root = styled.div` const Root = styled.div`
......
...@@ -21,7 +21,7 @@ const ReviewerReportAuthor = ({ ...@@ -21,7 +21,7 @@ const ReviewerReportAuthor = ({
downloadFile, downloadFile,
publicReport, publicReport,
reviewerName, reviewerName,
reviewerIndex, reviewerNumber,
recommendation, recommendation,
showOwner = false, showOwner = false,
report: { submittedOn }, report: { submittedOn },
...@@ -38,7 +38,7 @@ const ReviewerReportAuthor = ({ ...@@ -38,7 +38,7 @@ const ReviewerReportAuthor = ({
</Row> </Row>
)} )}
<Text customId ml={1} mr={1} whiteSpace="nowrap"> <Text customId ml={1} mr={1} whiteSpace="nowrap">
{`Reviewer ${reviewerIndex}`} {`Reviewer ${reviewerNumber}`}
</Text> </Text>
<DateParser timestamp={submittedOn}> <DateParser timestamp={submittedOn}>
{date => <Text>{date}</Text>} {date => <Text>{date}</Text>}
...@@ -78,7 +78,7 @@ export default compose( ...@@ -78,7 +78,7 @@ export default compose(
'reviewer.lastName', 'reviewer.lastName',
'', '',
)}`, )}`,
reviewerIndex: get(report, 'reviewerIndex', ''), reviewerNumber: get(report, 'reviewerNumber', ''),
})), })),
)(ReviewerReportAuthor) )(ReviewerReportAuthor)
......
...@@ -29,7 +29,7 @@ const report = { ...@@ -29,7 +29,7 @@ const report = {
submittedOn: 1538053600624, submittedOn: 1538053600624,
recommendation: 'publish', recommendation: 'publish',
recommendationType: 'review', recommendationType: 'review',
reviewerIndex: 1 reviewerNumber: 1
} }
const journal = { const journal = {
......
...@@ -41,9 +41,9 @@ const ReviewersTable = ({ ...@@ -41,9 +41,9 @@ const ReviewersTable = ({
invitation, invitation,
'person.lastName', 'person.lastName',
)}`}</Text> )}`}</Text>
{invitation.isAccepted && ( {invitation.reviewerNumber && (
<Text customId ml={1}> <Text customId ml={1}>
{renderAcceptedLabel(index)} Reviewer {invitation.reviewerNumber}
</Text> </Text>
)} )}
</td> </td>
...@@ -102,12 +102,7 @@ export default compose( ...@@ -102,12 +102,7 @@ export default compose(
withProps(({ invitations = [] }) => ({ withProps(({ invitations = [] }) => ({
invitations: orderBy(invitations, orderInvitations), invitations: orderBy(invitations, orderInvitations),
})), })),
withProps(({ invitations = [] }) => ({
firstAccepted: invitations.findIndex(i => i.hasAnswer && i.isAccepted),
})),
withHandlers({ withHandlers({
renderAcceptedLabel: ({ firstAccepted, invitations }) => index =>
`Reviewer ${index - firstAccepted + 1}`,
getInvitationStatus: () => ({ hasAnswer, isAccepted }) => { getInvitationStatus: () => ({ hasAnswer, isAccepted }) => {
if (!hasAnswer) return 'PENDING' if (!hasAnswer) return 'PENDING'
if (isAccepted) return 'ACCEPTED' if (isAccepted) return 'ACCEPTED'
......
import React from 'react' import React from 'react'
import { withProps, compose } from 'recompose' import { withProps, compose } from 'recompose'
import { get } from 'lodash'
import { ContextualBox, ReviewerReportAuthor, Row, Text } from '../' import {
ContextualBox,
ReviewerReportAuthor,
Row,
Text,
indexReviewers,
} from '../'
const SubmittedReportsNumberForAuthorReviews = ({ reports }) => ( const SubmittedReportsNumberForAuthorReviews = ({ reports }) => (
<Row fitContent justify="flex-end"> <Row fitContent justify="flex-end">
...@@ -16,12 +23,13 @@ const SubmittedReportsNumberForAuthorReviews = ({ reports }) => ( ...@@ -16,12 +23,13 @@ const SubmittedReportsNumberForAuthorReviews = ({ reports }) => (
) )
const AuthorReviews = ({ const AuthorReviews = ({
invitations, token,
journal, journal,
reports, reports,
fragment, fragment,
token, invitations,
getSignedUrl, getSignedUrl,
reviewerReports,
}) => }) =>
reports.length > 0 && ( reports.length > 0 && (
<ContextualBox <ContextualBox
...@@ -43,4 +51,24 @@ const AuthorReviews = ({ ...@@ -43,4 +51,24 @@ const AuthorReviews = ({
</ContextualBox> </ContextualBox>
) )
export default compose(withProps())(AuthorReviews) export default compose(
withProps(
({
invitations = [],
publonReviewers = [],
reviewerReports = [],
currentUser,
}) => ({
token: get(currentUser, 'token', ''),
publonReviewers,
invitations: invitations.map(i => ({
...i,
review: reviewerReports.find(r => r.userId === i.userId),
})),
reports: indexReviewers(
reviewerReports.filter(r => r.submittedOn),
invitations,
),
}),
),
)(AuthorReviews)
...@@ -28,7 +28,7 @@ const reports = [ ...@@ -28,7 +28,7 @@ const reports = [
submittedOn: 1539339580826, submittedOn: 1539339580826,
recommendation: 'minor', recommendation: 'minor',
recommendationType: 'review', recommendationType: 'review',
reviewerIndex: 1, reviewerNumber: 1,
}, },
{ {
id: '21258b47-aba5-4597-926e-765458c4fda2', id: '21258b47-aba5-4597-926e-765458c4fda2',
...@@ -45,7 +45,7 @@ const reports = [ ...@@ -45,7 +45,7 @@ const reports = [
submittedOn: 1539689169611, submittedOn: 1539689169611,
recommendation: 'publish', recommendation: 'publish',
recommendationType: 'review', recommendationType: 'review',
reviewerIndex: 2, reviewerNumber: 2,
}, },
] ]
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
ContextualBox, ContextualBox,
ReviewersTable, ReviewersTable,
PublonsTable, PublonsTable,
indexReviewers,
ReviewerReport, ReviewerReport,
InviteReviewers, InviteReviewers,
ReviewerBreakdown, ReviewerBreakdown,
...@@ -118,14 +119,14 @@ const ReviewerDetails = ({ ...@@ -118,14 +119,14 @@ const ReviewerDetails = ({
{reports.length === 0 && ( {reports.length === 0 && (
<Text align="center">No reports submitted yet.</Text> <Text align="center">No reports submitted yet.</Text>
)} )}
{reports.map((report, index) => ( {reports.map(report => (
<ReviewerReport <ReviewerReport
journal={journal} journal={journal}
key={report.id} key={report.id}
onDownload={downloadFile} onDownload={downloadFile}
onPreview={previewFile} onPreview={previewFile}
report={report} report={report}
reviewerIndex={index + 1} reviewerNumber={report.reviewerNumber}
showOwner showOwner
/> />
))} ))}
...@@ -154,7 +155,10 @@ export default compose( ...@@ -154,7 +155,10 @@ export default compose(
...i, ...i,
review: reviewerReports.find(r => r.userId === i.userId), review: reviewerReports.find(r => r.userId === i.userId),
})), })),
reports: reviewerReports.filter(r => r.submittedOn), reports: indexReviewers(
reviewerReports.filter(r => r.submittedOn),
invitations,
),
}), }),
), ),
withProps(({ currentUser }) => ({ withProps(({ currentUser }) => ({
......
import { get, chain } from 'lodash' import { get, chain, find } from 'lodash'
export const handleError = fn => e => { export const handleError = fn => e => {
fn(get(JSON.parse(e.response), 'error', 'Oops! Something went wrong!')) fn(get(JSON.parse(e.response), 'error', 'Oops! Something went wrong!'))
...@@ -10,3 +10,14 @@ export const getReportComments = ({ report, isPublic = false }) => ...@@ -10,3 +10,14 @@ export const getReportComments = ({ report, isPublic = false }) =>
.find(c => c.public === isPublic) .find(c => c.public === isPublic)
.get('content') .get('content')
.value() .value()
export const indexReviewers = (reports = [], invitations = []) => {
reports.forEach(report => {
report.reviewerNumber = get(
find(invitations, ['userId', report.userId]),
'reviewerNumber',
0,
)
})
return reports
}
import React, { Fragment } from 'react' 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 { H2, H4, DateParser, Button } from '@pubsweet/ui'
import { import {
compose, compose,
...@@ -121,6 +121,7 @@ export default compose( ...@@ -121,6 +121,7 @@ export default compose(
revokeInvitation, revokeInvitation,
pendingInvitation = {}, pendingInvitation = {},
handlingEditors = [], handlingEditors = [],
isLatestVersion,
currentUser: { currentUser: {
permissions: { canAssignHE }, permissions: { canAssignHE },
id: currentUserId, id: currentUserId,
...@@ -128,31 +129,45 @@ export default compose( ...@@ -128,31 +129,45 @@ export default compose(
editorInChief, editorInChief,
}, },
collection: { handlingEditor }, collection: { handlingEditor },
collection,
currentUser, currentUser,
}) => () => { }) => () => {
if (pendingInvitation.userId === currentUserId) { if (pendingInvitation.userId === currentUserId) {
return <Text ml={1}>Invited</Text> return <Text ml={1}>Invited</Text>
} }
if (pendingInvitation.userId && (admin || editorInChief)) { const invitedHeId =
get(pendingInvitation, 'userId', false) ||
get(heInvitation, 'userId', false)
if (invitedHeId && (admin || editorInChief)) {
const person = chain(handlingEditors) const person = chain(handlingEditors)
.filter(he => he.id === pendingInvitation.userId) .filter(he => he.id === invitedHeId)
.map(he => ({ ...he, name: `${he.firstName} ${he.lastName}` })) .map(he => ({ ...he, name: `${he.firstName} ${he.lastName}` }))
.first() .first()
.value() .value()
let invitedHe = {}
if (get(pendingInvitation, 'userId', false)) {
invitedHe = pendingInvitation
} else if (get(heInvitation, 'userId', false)) {
invitedHe = heInvitation
}
return ( return (
<PersonInvitation <PersonInvitation
isFetching={isFetching} isFetching={isFetching}
isLatestVersion={isLatestVersion}
ml={1} ml={1}
withName withName
{...pendingInvitation} {...invitedHe}
onResend={resendInvitation} onResend={resendInvitation}
onRevoke={revokeInvitation} onRevoke={revokeInvitation}
person={person} person={person}
/> />
) )
} }
if (!isEmpty(pendingInvitation)) {
return <Text ml={1}>{handlingEditor.name}</Text>
}
if (heInvitation) { if (heInvitation) {
return <Text ml={1}>{handlingEditor.name}</Text> return <Text ml={1}>{handlingEditor.name}</Text>
} }
...@@ -170,7 +185,7 @@ export default compose( ...@@ -170,7 +185,7 @@ export default compose(
</Button> </Button>
) )
} }
return <Text ml={1}>Assigned</Text> return <Text ml={1}>Unassigned</Text>
}, },
}), }),
setDisplayName('ManuscriptHeader'), setDisplayName('ManuscriptHeader'),
......
...@@ -7,5 +7,6 @@ module.exports = { ...@@ -7,5 +7,6 @@ module.exports = {
collectionReviewCompletedID: chance.guid(), collectionReviewCompletedID: chance.guid(),
collectionNoInvitesID: chance.guid(), collectionNoInvitesID: chance.guid(),
twoVersionsCollectionId: chance.guid(), twoVersionsCollectionId: chance.guid(),
oneReviewedFragmentCollectionID: chance.guid(),
noEditorRecomedationCollectionID: chance.guid(), noEditorRecomedationCollectionID: chance.guid(),
} }
...@@ -17,6 +17,7 @@ const { ...@@ -17,6 +17,7 @@ const {
collectionReviewCompletedID, collectionReviewCompletedID,
collectionNoInvitesID, collectionNoInvitesID,
twoVersionsCollectionId, twoVersionsCollectionId,
oneReviewedFragmentCollectionID,
noEditorRecomedationCollectionID, noEditorRecomedationCollectionID,
} = require('./collectionIDs') } = require('./collectionIDs')
...@@ -30,6 +31,7 @@ const collections = { ...@@ -30,6 +31,7 @@ const collections = {
fragments: [fragment.id], fragments: [fragment.id],
owners: [user.id], owners: [user.id],
save: jest.fn(() => collections.collection), save: jest.fn(() => collections.collection),
getFragments: jest.fn(() => [fragment]),
invitations: [ invitations: [
{ {
id: chance.guid(), id: chance.guid(),
...@@ -73,6 +75,7 @@ const collections = { ...@@ -73,6 +75,7 @@ const collections = {
fragments: [fragment.id], fragments: [fragment.id],
owners: [user.id], owners: [user.id],
save: jest.fn(() => collections.collection), save: jest.fn(() => collections.collection),
getFragments: jest.fn(() => [fragment]),
invitations: [ invitations: [
{ {
id: chance.guid(), id: chance.guid(),
...@@ -115,6 +118,7 @@ const collections = { ...@@ -115,6 +118,7 @@ const collections = {
fragments: [fragment1.id, noInvitesFragment.id], fragments: [fragment1.id, noInvitesFragment.id],
owners: [user.id], owners: [user.id],
save: jest.fn(() => collections.collection2), save: jest.fn(() => collections.collection2),
getFragments: jest.fn(() => [fragment1, noInvitesFragment]),
invitations: [ invitations: [
{ {
id: chance.guid(), id: chance.guid(),
...@@ -159,6 +163,7 @@ const collections = { ...@@ -159,6 +163,7 @@ const collections = {
created: chance.timestamp(), created: chance.timestamp(),
customId: '0000001', customId: '0000001',
fragments: [reviewCompletedFragment.id], fragments: [reviewCompletedFragment.id],
getFragments: jest.fn(() => [reviewCompletedFragment]),
invitations: [ invitations: [
{ {
id: chance.guid(), id: chance.guid(),
...@@ -189,6 +194,7 @@ const collections = { ...@@ -189,6 +194,7 @@ const collections = {
fragments: [fragment.id, reviewCompletedFragment.id], fragments: [fragment.id, reviewCompletedFragment.id],
owners: [user.id], owners: [user.id],
save: jest.fn(() => collections.collection), save: jest.fn(() => collections.collection),
getFragments: jest.fn(() => [fragment, reviewCompletedFragment]),
invitations: [ invitations: [
{ {
id: chance.guid(), id: chance.guid(),
...@@ -219,6 +225,7 @@ const collections = { ...@@ -219,6 +225,7 @@ const collections = {
fragments: [], fragments: [],
owners: [user.id], owners: [user.id],
save: jest.fn(() => collections.collection), save: jest.fn(() => collections.collection),
getFragments: jest.fn(() => []),
customId: chance.natural({ min: 999999, max: 9999999 }), customId: chance.natural({ min: 999999, max: 9999999 }),
}, },
noEditorRecomedationCollection: { noEditorRecomedationCollection: {
...@@ -228,6 +235,7 @@ const collections = { ...@@ -228,6 +235,7 @@ const collections = {
fragments: [noEditorRecomedationFragment.id], fragments: [noEditorRecomedationFragment.id],
owners: [user.id], owners: [user.id],
save: jest.fn(() => collections.noEditorRecomedationCollection), save: jest.fn(() => collections.noEditorRecomedationCollection),
getFragments: jest.fn(() => [noEditorRecomedationFragment]),
invitations: [ invitations: [
{ {
id: chance.guid(), id: chance.guid(),
...@@ -263,6 +271,37 @@ const collections = { ...@@ -263,6 +271,37 @@ const collections = {
}, },
status: 'reviewCompleted', status: 'reviewCompleted',
}, },
oneReviewedFragmentCollection: {
id: oneReviewedFragmentCollectionID,
title: chance.sentence(),
type: 'collection',
fragments: [reviewCompletedFragment.id, noInvitesFragment.id],
owners: [user.id],
save: jest.fn(() => collections.collection),
getFragments: jest.fn(() => [reviewCompletedFragment, noInvitesFragment]),
invitations: [
{
id: chance.guid(),
role: 'handlingEditor',
hasAnswer: true,
isAccepted: false,
userId: handlingEditor.id,
invitedOn: chance.timestamp(),
respondedOn: null,
},
],
handlingEditor: {
id: handlingEditor.id,
hasAnswer: false,
isAccepted: false,
email: handlingEditor.email,
invitedOn: chance.timestamp(),
respondedOn: null,
name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
},
status: 'revisionRequested',
customId: chance.natural({ min: 999999, max: 9999999 }),
},
} }
module.exports = collections module.exports = collections
...@@ -340,6 +340,7 @@ const fragments = { ...@@ -340,6 +340,7 @@ const fragments = {
invitedOn: chance.timestamp(), invitedOn: chance.timestamp(),
isAccepted: true, isAccepted: true,
respondedOn: chance.timestamp(), respondedOn: chance.timestamp(),
reviewerNumber: 2,
}, },
{ {
id: chance.guid(), id: chance.guid(),
......
const { findLast, get } = require('lodash') const { findLast, isEmpty, maxBy, get, flatMap } = require('lodash')
const Fragment = require('./Fragment') const Fragment = require('./Fragment')
...@@ -109,6 +109,27 @@ class Collection { ...@@ -109,6 +109,27 @@ class Collection {
return lastName || firstName return lastName || firstName
} }
async getReviewerNumber({ userId }) {
const allCollectionFragments = await this.collection.getFragments()
const allCollectionInvitations = flatMap(
allCollectionFragments,
fragment => fragment.invitations,
)
const allNumberedInvitationsForUser = allCollectionInvitations
.filter(invite => invite.userId === userId)
.filter(invite => invite.reviewerNumber)
if (isEmpty(allNumberedInvitationsForUser)) {
const maxReviewerNumber = get(
maxBy(allCollectionInvitations, 'reviewerNumber'),
'reviewerNumber',
0,
)
return maxReviewerNumber + 1
}
return allNumberedInvitationsForUser[0].reviewerNumber
}
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
hasAtLeastOneReviewReport(fragments) { hasAtLeastOneReviewReport(fragments) {
return fragments.some(fragment => return fragments.some(fragment =>
...@@ -126,17 +147,19 @@ class Collection { ...@@ -126,17 +147,19 @@ class Collection {
[], [],
) )
const lastRecommendationByHE = findLast( const lastEditorRecommendation = findLast(
previousVersionRecommendations, previousVersionRecommendations,
recommendation => recommendation =>
recommendation.userId === this.collection.handlingEditor.id &&
recommendation.recommendationType === 'editorRecommendation', recommendation.recommendationType === 'editorRecommendation',
) )
if (lastRecommendationByHE.recommendation === 'minor') {
if (lastEditorRecommendation.recommendation === 'minor') {
return this.hasAtLeastOneReviewReport(fragments) return this.hasAtLeastOneReviewReport(fragments)
} else if (lastRecommendationByHE.recommendation === 'major') { } else if (lastEditorRecommendation.recommendation === 'major') {
return fragmentHelper.hasReviewReport() return fragmentHelper.hasReviewReport()
} }
return false
} }
async getAllFragments({ FragmentModel }) { async getAllFragments({ FragmentModel }) {
......
...@@ -10,11 +10,65 @@ const { Collection, Fragment } = require('../Helper') ...@@ -10,11 +10,65 @@ const { Collection, Fragment } = require('../Helper')
describe('Collection helper', () => { describe('Collection helper', () => {
let testFixtures = {} let testFixtures = {}
let models let models
beforeEach(() => { beforeEach(() => {
testFixtures = cloneDeep(fixtures) testFixtures = cloneDeep(fixtures)
models = Model.build(testFixtures) models = Model.build(testFixtures)
}) })
describe('getReviewerNumber', () => {
it('should assign reviewer number 1 on invitation if no other reviewer numbers exist', async () => {
const { collection } = testFixtures.collections
const { reviewer } = testFixtures.users
const collectionHelper = new Collection({ collection })
const reviewerNumber = await collectionHelper.getReviewerNumber({
userId: reviewer.id,
})
expect(reviewerNumber).toBe(1)
})
it('should assign next reviewer number on invitation if another reviewer numbers exist', async () => {
const { collectionReviewCompleted } = testFixtures.collections
const { reviewer } = testFixtures.users
const collectionHelper = new Collection({
collection: collectionReviewCompleted,
})
const reviewerNumber = await collectionHelper.getReviewerNumber({
userId: reviewer.id,
})
expect(reviewerNumber).toBe(3)
})
it('should keep reviewer number across fragment versions', async () => {
const { oneReviewedFragmentCollection } = testFixtures.collections
const { answerReviewer } = testFixtures.users
const collectionHelper = new Collection({
collection: oneReviewedFragmentCollection,
})
const reviewerNumber = await collectionHelper.getReviewerNumber({
userId: answerReviewer.id,
})
expect(reviewerNumber).toBe(2)
})
it('should assign next reviewer number across fragment versions', async () => {
const { oneReviewedFragmentCollection } = testFixtures.collections
const { reviewer } = testFixtures.users
const collectionHelper = new Collection({
collection: oneReviewedFragmentCollection,
})
const reviewerNumber = await collectionHelper.getReviewerNumber({
userId: reviewer.id,
})
expect(reviewerNumber).toBe(3)
})
})
describe('hasAtLeastOneReviewReport', () => { describe('hasAtLeastOneReviewReport', () => {
it('should return true if collection has at least one report from reviewers.', async () => { it('should return true if collection has at least one report from reviewers.', async () => {
const { collection } = testFixtures.collections const { collection } = testFixtures.collections
......
...@@ -39,7 +39,7 @@ const CollectionsInvitations = app => { ...@@ -39,7 +39,7 @@ const CollectionsInvitations = app => {
require(`${routePath}/post`)(app.locals.models), require(`${routePath}/post`)(app.locals.models),
) )
/** /**
* @api {delete} /api/collections/:collectionId/invitations/:invitationId Delete invitation * @api {delete} /api/collections/:collectionId/invitations/:invitationId Delete invitation (or revoke HE if invitation is accepted)
* @apiGroup CollectionsInvitations * @apiGroup CollectionsInvitations
* @apiParam {collectionId} collectionId Collection id * @apiParam {collectionId} collectionId Collection id
* @apiParam {invitationId} invitationId Invitation id * @apiParam {invitationId} invitationId Invitation id
......
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