diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 8a6ef140784a77427e9da1f5095a39a3148efaea..f265fd9dc0b366803c2eac1c74d5772e6ba79269 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -1,5 +1,5 @@ -import { get, has, last, chain, some } from 'lodash' import { selectCurrentUser } from 'xpub-selectors' +import { get, has, last, chain, some } from 'lodash' export const isHEToManuscript = (state, collectionId = '') => { const { id = '', isAccepted = false } = chain(state) @@ -199,9 +199,7 @@ export const getHERecommendation = (state, collectionId, fragmentId) => { } const canMakeDecisionStatuses = ['submitted', 'pendingApproval'] -export const canMakeDecision = (state, collection = {}, fragment = {}) => { - if (get(fragment, 'id', '') !== last(get(collection, 'fragments', []))) - return false +export const canMakeDecision = (state, collection = {}) => { const status = get(collection, 'status', 'draft') const isEIC = currentUserIs(state, 'adminEiC') @@ -372,6 +370,9 @@ export const canMakeHERecommendation = (state, { collection, statuses }) => { return statusImportance > 1 && statusImportance < 9 && validHE } +export const getFragmentAuthorResponse = (state, fragmentId) => + get(state, `fragments.${fragmentId}.responseToReviewers`, {}) + // #region Editorial and reviewer recommendations export const getFragmentRecommendations = (state, fragmentId) => get(state, `fragments.${fragmentId}.recommendations`, []) @@ -409,18 +410,6 @@ export const getOwnSubmittedRecommendation = (state, fragmentId) => ) .value() -const canMakeRecommendationStatuses = [ - 'heAssigned', - 'underReview', - 'reviewCompleted', -] -export const canMakeRecommendation = (state, collection, fragment = {}) => { - if (fragment.id !== last(get(collection, 'fragments', []))) return false - const isHE = isHEToManuscript(state, get(collection, 'id', '')) - const status = get(collection, 'status', 'draft') - return isHE && canMakeRecommendationStatuses.includes(status) -} - export const canSubmitRevision = (state, fragment = {}) => { const userId = get(state, 'currentUser.user.id') const fragmentAuthors = chain(fragment) @@ -450,3 +439,14 @@ export const getVersionOptions = (state, collection = {}) => { .reverse() .value() } + +export const canReview = (state, collection = {}, fragment = {}) => { + const fragmentId = get(fragment, 'id', false) + + if (!fragmentId) return false + + const isReviewer = currentUserIsReviewer(state, fragmentId) + if (!isReviewer) return false + + return get(collection, 'status', 'draft') === 'underReview' +} diff --git a/packages/component-faraday-ui/src/ActionLink.js b/packages/component-faraday-ui/src/ActionLink.js index 9f27cec30d1ba8e1584fed2f753653c952e23f77..05ad25cd28ff0ec15c333469b13f6ec6c16e9402 100644 --- a/packages/component-faraday-ui/src/ActionLink.js +++ b/packages/component-faraday-ui/src/ActionLink.js @@ -78,7 +78,7 @@ const Root = styled.div` ${marginHelper}; ${paddingHelper}; - height: max-content; + height: ${props => (props.height ? `${props.height}px` : 'max-content')}; width: max-content; & span { diff --git a/packages/component-faraday-ui/src/AuthorReply.js b/packages/component-faraday-ui/src/AuthorReply.js new file mode 100644 index 0000000000000000000000000000000000000000..22649c9636aba91856d4fef278b074951b73b6b7 --- /dev/null +++ b/packages/component-faraday-ui/src/AuthorReply.js @@ -0,0 +1,53 @@ +import React from 'react' +import { get } from 'lodash' +import { withProps } from 'recompose' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { DateParser } from '@pubsweet/ui' + +import { Label, Item, Row, Text } from './' + +const submittingAuthor = authors => { + const thisAuthor = authors.filter(e => e.isSubmitting) + return thisAuthor[0] +} + +const AuthorReply = ({ reply, authorName, submittedOn }) => ( + <Root> + <Row justify="space-between" mb={2}> + <Item justify="flex-end"> + <Row mb={1}> + <Item vertical> + <Label mb={1 / 2}>Author Reply</Label> + <Text>{reply}</Text> + </Item> + </Row> + <Text ml={1} mr={1} whiteSpace="nowrap"> + {authorName} + </Text> + <DateParser timestamp={submittedOn}> + {date => <Text>{date}</Text>} + </DateParser> + </Item> + </Row> + </Root> +) + +export default withProps(({ fragment: { authors, submitted } }) => ({ + submittedOn: submitted, + authorName: `${get(submittingAuthor(authors), 'firstName', '')} ${get( + submittingAuthor(authors), + 'lastName', + '', + )}`, +}))(AuthorReply) + +// #region styles +const Root = styled.div` + background-color: ${th('colorBackgroundHue')}; + border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBackgroundHue3')}; + border-radius: ${th('borderRadius')}; + padding: calc(${th('gridUnit')} * 2); + margin: ${th('gridUnit')}; +` +// #endregion diff --git a/packages/component-faraday-ui/src/AuthorReply.md b/packages/component-faraday-ui/src/AuthorReply.md new file mode 100644 index 0000000000000000000000000000000000000000..0548cdd8dc8e29ab92e88edc197301ea6b7b3eb5 --- /dev/null +++ b/packages/component-faraday-ui/src/AuthorReply.md @@ -0,0 +1,77 @@ +Reviewer report. + +```js +const fragment = { + "id": "1378b0e4-5c29-46a6-8afe-2f5ec1b13899", + "type": "fragment", + "files": { + "manuscripts": [ + { + "id": "e891d6dd-fe46-472d-87db-5c80fdd48ac2/9e103d6a-dd0e-4d1e-b6ff-9b8383579649", + "name": "Evolutionary Programming.pdf", + "size": 81618, + "originalName": "Evolutionary Programming.pdf" + } + ], + "supplementary": [] + }, + "owners": [ + { + "id": "f3a16660-ae1e-4ab2-9ae4-220a0c7ac575", + "username": "tania.fecheta+a@thinslices.com" + } + ], + "authors": [ + { + "id": "f3a16660-ae1e-4ab2-9ae4-220a0c7ac575", + "title": "mrs", + "country": "AL", + "lastName": "Tania", + "firstName": "Author", + "affiliation": "Ts", + "isSubmitting": true, + "isCorresponding": true + } + ], + "created": "2018-10-30T07:33:26.128Z", + "version": 2, + "metadata": { + "type": "research", + "title": "Major revision and new version sunmited", + "journal": "Bioinorganic Chemistry and Applications", + "abstract": "something something" + }, + "conflicts": { + "hasFunding": "", + "hasConflicts": "no", + "hasDataAvailability": "" + }, + "submitted": 1540884806175, + "invitations": [ + { + "id": "7b50667d-883c-43d4-aea5-71455e251801", + "role": "reviewer", + "type": "invitation", + "userId": "b7554926-89dc-4b8b-b4d7-cd1bcc51f2de", + "hasAnswer": true, + "invitedOn": 1540884401711, + "isAccepted": true, + "respondedOn": 1540884410543 + } + ], + "collectionId": "383a5314-788c-49e5-aa00-617426b5c7c7", + "declarations": { + "agree": true + }, + "fragmentType": "version", + "responseToReviewers": { + "file": null, + "content": "i changed that thing" + }, + "recommendations": [] +}; + +<AuthorReply + fragment={fragment} +/> +``` diff --git a/packages/component-faraday-ui/src/ManuscriptCard.js b/packages/component-faraday-ui/src/ManuscriptCard.js index ae360b12e7ae2b3220528f7e771b5cac322bd377..17e9e176354403406b6066cf5b09f589a9a93cfa 100644 --- a/packages/component-faraday-ui/src/ManuscriptCard.js +++ b/packages/component-faraday-ui/src/ManuscriptCard.js @@ -24,6 +24,7 @@ import { OpenModal } from './modals' const ManuscriptCard = ({ onDelete, canDelete, + isFetching, onCardClick, canViewReports, fragment = {}, @@ -87,16 +88,17 @@ const ManuscriptCard = ({ <Item justify="flex-end" onClick={e => e.stopPropagation()}> <OpenModal confirmText="Delete" + isFetching={isFetching} modalKey={`delete-${collId}`} onConfirm={onDelete} title="Are you sure you want to delete this submission?" > - {onClickEvent => ( + {showModal => ( <ActionLink + height={16} icon="trash" - onClick={onClickEvent} + onClick={showModal} size="small" - style={{ height: 16 }} > Delete </ActionLink> diff --git a/packages/component-faraday-ui/src/ReviewerReportAuthor.js b/packages/component-faraday-ui/src/ReviewerReportAuthor.js index 297653497f8a77be8c9bf20135ca64c7c21a54a4..4df5745b8ce86a109bad547466f3fe5ff57f94de 100644 --- a/packages/component-faraday-ui/src/ReviewerReportAuthor.js +++ b/packages/component-faraday-ui/src/ReviewerReportAuthor.js @@ -1,16 +1,24 @@ import React, { Fragment } from 'react' import { get } from 'lodash' -import { withProps } from 'recompose' +import { withProps, compose } from 'recompose' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' import { DateParser } from '@pubsweet/ui' -import { Label, Item, FileItem, Row, Text } from './' +import { + Label, + Item, + FileItem, + Row, + Text, + withFilePreview, + withFileDownload, +} from './' const ReviewerReportAuthor = ({ - onPreview, - onDownload, reviewFile, + previewFile, + downloadFile, publicReport, reviewerName, reviewerIndex, @@ -45,8 +53,8 @@ const ReviewerReportAuthor = ({ <Item flex={0} mr={1}> <FileItem item={reviewFile} - onDownload={onDownload} - onPreview={onPreview} + onDownload={downloadFile} + onPreview={previewFile} /> </Item> </Row> @@ -55,20 +63,24 @@ const ReviewerReportAuthor = ({ </Root> ) -export default withProps(({ report, journal: { recommendations = [] } }) => ({ - recommendation: get( - recommendations.find(r => r.value === report.recommendation), - 'label', - ), - reviewFile: get(report, 'comments.0.files.0'), - publicReport: get(report, 'comments.0.content'), - reviewerName: `${get(report, 'reviewer.firstName', '')} ${get( - report, - 'reviewer.lastName', - '', - )}`, - reviewerIndex: get(report, 'reviewerIndex', ''), -}))(ReviewerReportAuthor) +export default compose( + withFilePreview, + withFileDownload, + withProps(({ report, journal: { recommendations = [] } }) => ({ + recommendation: get( + recommendations.find(r => r.value === report.recommendation), + 'label', + ), + reviewFile: get(report, 'comments.0.files.0'), + publicReport: get(report, 'comments.0.content'), + reviewerName: `${get(report, 'reviewer.firstName', '')} ${get( + report, + 'reviewer.lastName', + '', + )}`, + reviewerIndex: get(report, 'reviewerIndex', ''), + })), +)(ReviewerReportAuthor) // #region styles const Root = styled.div` diff --git a/packages/component-faraday-ui/src/Tag.js b/packages/component-faraday-ui/src/Tag.js index 856a69c88fb0f25ed6a64ed3368221239d961d81..fd6ce0316577b29f1ca95619cdd180a860209c54 100644 --- a/packages/component-faraday-ui/src/Tag.js +++ b/packages/component-faraday-ui/src/Tag.js @@ -1,27 +1,41 @@ -import styled, { css } from 'styled-components' +import { has } from 'lodash' import { th } from '@pubsweet/ui-toolkit' +import styled, { css } from 'styled-components' import { marginHelper } from './styledHelpers' -export const tagCSS = ({ status }) => - status - ? css` - padding: calc(${th('gridUnit')} / 4) ${th('gridUnit')}; - height: 24px; - font-family: ${th('fontHeading')}; - ` - : css` - height: 14px; - font-size: 85%; - padding: calc(${th('gridUnit')} / 4) calc(${th('gridUnit')} / 4) 0px; - margin-right: 1px; - font-family: ${th('fontInterface')}; - ` +const tagCSS = props => { + if (has(props, 'oldStatus')) { + return css` + background-color: ${th('colorFurnitureHue')}; + height: calc(${th('gridUnit')} * 3) + font-weight: ${th('tag.fontWeight')}; + padding: calc(${th('gridUnit')} / 2) ${th('gridUnit')} 0px + ${th('gridUnit')}; + ` + } + + if (has(props, `status`)) { + return css` + background-color: ${th('tag.statusBackgroundColor')}; + padding: calc(${th('gridUnit')} / 4) ${th('gridUnit')}; + height: 24px; + font-family: ${th('fontHeading')}; + ` + } + + return css` + background-color: ${th('tag.backgroundColor')}; + height: 14px; + font-size: 85%; + padding: calc(${th('gridUnit')} / 4) calc(${th('gridUnit')} / 4) 0px; + margin-right: 1px; + font-family: ${th('fontInterface')}; + ` +} /** @component */ export default styled.div` - background-color: ${({ status }) => - status ? th('tag.statusBackgroundColor') : th('tag.backgroundColor')}; border-radius: ${th('tag.borderRadius') ? th('tag.borderRadius') : th('borderRadius')}; diff --git a/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.js b/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.js index 105b76fc04c6a4046c794398157ee770135ad538..5518b3ebfa9c3803816c871f4f8b32b717c468d3 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.js +++ b/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.js @@ -15,7 +15,14 @@ const SubmittedReportsNumberForAuthorReviews = ({ reports }) => ( </Row> ) -const AuthorReviews = ({ invitations, journal, reports, fragment }) => +const AuthorReviews = ({ + invitations, + journal, + reports, + fragment, + token, + getSignedUrl, +}) => reports.length > 0 && ( <ContextualBox label="Reviewer Reports" @@ -24,11 +31,13 @@ const AuthorReviews = ({ invitations, journal, reports, fragment }) => <SubmittedReportsNumberForAuthorReviews reports={reports.length} /> } > - {reports.map((r, i) => ( + {reports.map(r => ( <ReviewerReportAuthor + getSignedUrl={getSignedUrl} journal={journal} key={r.id} - report={reports[i]} + report={r} + token={token} /> ))} </ContextualBox> diff --git a/packages/component-faraday-ui/src/contextualBoxes/ResponseToRevisionRequest.js b/packages/component-faraday-ui/src/contextualBoxes/ResponseToRevisionRequest.js new file mode 100644 index 0000000000000000000000000000000000000000..4d3d4b181d864cd2b5c829ad06902b058a3c09ec --- /dev/null +++ b/packages/component-faraday-ui/src/contextualBoxes/ResponseToRevisionRequest.js @@ -0,0 +1,21 @@ +import React from 'react' + +import { ContextualBox, AuthorReply } from '../' + +const ResponseToRevisionRequest = ({ + fragment, + authorReply, + toggle, + expanded, +}) => ( + <ContextualBox + expanded={expanded} + label="Response to Revision Request" + mb={2} + toggle={toggle} + > + <AuthorReply fragment={fragment} reply={authorReply} /> + </ContextualBox> +) + +export default ResponseToRevisionRequest diff --git a/packages/component-faraday-ui/src/contextualBoxes/ResponseToRevisionRequest.md b/packages/component-faraday-ui/src/contextualBoxes/ResponseToRevisionRequest.md new file mode 100644 index 0000000000000000000000000000000000000000..eae07412e44fa24ea983e46dffd6ca01bef3713b --- /dev/null +++ b/packages/component-faraday-ui/src/contextualBoxes/ResponseToRevisionRequest.md @@ -0,0 +1,75 @@ +ResponseToRevisionRequest. + +```js +const fragment = { + "id": "1378b0e4-5c29-46a6-8afe-2f5ec1b13899", + "type": "fragment", + "files": { + "manuscripts": [ + { + "id": "e891d6dd-fe46-472d-87db-5c80fdd48ac2/9e103d6a-dd0e-4d1e-b6ff-9b8383579649", + "name": "Evolutionary Programming.pdf", + "size": 81618, + "originalName": "Evolutionary Programming.pdf" + } + ], + "supplementary": [] + }, + "owners": [ + { + "id": "f3a16660-ae1e-4ab2-9ae4-220a0c7ac575", + "username": "tania.fecheta+a@thinslices.com" + } + ], + "authors": [ + { + "id": "f3a16660-ae1e-4ab2-9ae4-220a0c7ac575", + "title": "mrs", + "country": "AL", + "lastName": "Tania", + "firstName": "Author", + "affiliation": "Ts", + "isSubmitting": true, + "isCorresponding": true + } + ], + "created": "2018-10-30T07:33:26.128Z", + "version": 2, + "metadata": { + "type": "research", + "title": "Major revision and new version sunmited", + "journal": "Bioinorganic Chemistry and Applications", + "abstract": "something something" + }, + "conflicts": { + "hasFunding": "", + "hasConflicts": "no", + "hasDataAvailability": "" + }, + "submitted": 1540884806175, + "invitations": [ + { + "id": "7b50667d-883c-43d4-aea5-71455e251801", + "role": "reviewer", + "type": "invitation", + "userId": "b7554926-89dc-4b8b-b4d7-cd1bcc51f2de", + "hasAnswer": true, + "invitedOn": 1540884401711, + "isAccepted": true, + "respondedOn": 1540884410543 + } + ], + "collectionId": "383a5314-788c-49e5-aa00-617426b5c7c7", + "declarations": { + "agree": true + }, + "fragmentType": "version", + "responseToReviewers": { + "file": null, + "content": "i changed that thing" + }, + "recommendations": [] +}; + +<ResponseToRevisionRequest fragment={fragment} /> +``` diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js index efa929afe32fd35476948eca48e366414aaa3c15..d8a41b821abe0639bbb6f9cfbe6ffe17b9f75189 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js @@ -40,6 +40,7 @@ const ReviewerDetails = ({ highlight, canViewReviewersDetails, authorCanViewReportsDetails, + isLatestVersion, ...rest }) => ( <ContextualBox @@ -62,17 +63,18 @@ const ReviewerDetails = ({ > <H4>Reviewer Details</H4> </TabButton> - {canInviteReviewers && ( - <TabButton - data-test-id="reviewer-tab-suggestions" - ml={1} - mr={1} - onClick={() => changeTab(2)} - selected={selectedTab === 2} - > - <H4>Reviewer Suggestions</H4> - </TabButton> - )} + {canInviteReviewers && + isLatestVersion && ( + <TabButton + data-test-id="reviewer-tab-suggestions" + ml={1} + mr={1} + onClick={() => changeTab(2)} + selected={selectedTab === 2} + > + <H4>Reviewer Suggestions</H4> + </TabButton> + )} <TabButton data-test-id="reviewer-tab-reports" ml={1} @@ -87,12 +89,13 @@ const ReviewerDetails = ({ <TabContent> {selectedTab === 0 && ( <Fragment> - {canInviteReviewers && ( - <InviteReviewers - modalKey="invite-reviewers" - onInvite={onInviteReviewer} - /> - )} + {canInviteReviewers && + isLatestVersion && ( + <InviteReviewers + modalKey="invite-reviewers" + onInvite={onInviteReviewer} + /> + )} <ReviewersTable invitations={invitations} @@ -101,14 +104,15 @@ const ReviewerDetails = ({ /> </Fragment> )} - {selectedTab === 2 && ( - <PublonsTable - onInvite={onInvitePublonReviewer} - publonsError={fetchingError} - publonsFetching={isFetching} - reviewers={publonReviewers} - /> - )} + {selectedTab === 2 && + isLatestVersion && ( + <PublonsTable + onInvite={onInvitePublonReviewer} + publonsError={fetchingError} + publonsFetching={isFetching} + reviewers={publonReviewers} + /> + )} {selectedTab === 1 && ( <Fragment> {reports.length === 0 && ( diff --git a/packages/component-faraday-ui/src/contextualBoxes/index.js b/packages/component-faraday-ui/src/contextualBoxes/index.js index d3ad70c5ba82387ff832effb11447534d18e4b15..97a41d51e4791b8f11f3d53cbd355de93d516cd8 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/index.js +++ b/packages/component-faraday-ui/src/contextualBoxes/index.js @@ -3,3 +3,6 @@ export { default as ReviewerDetails } from './ReviewerDetails' export { default as HERecommendation } from './HERecommendation' export { default as ReviewerReportForm } from './ReviewerReportForm' export { default as AuthorReviews } from './AuthorReviews' +export { + default as ResponseToRevisionRequest, +} from './ResponseToRevisionRequest' diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 4bfa4af165b2ecd8865299970a1ebe90428a1cfb..a99b60e4c1b2e18f15810beb84255aecd4ad4260 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -6,6 +6,7 @@ export * from './gridItems' export { default as ActionLink } from './ActionLink' export { default as AuthorWithTooltip } from './AuthorWithTooltip' +export { default as AuthorReply } from './AuthorReply' export { default as Logo } from './Logo' export { default as AppBar } from './AppBar' export { default as AppBarMenu } from './AppBarMenu' diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js index b7e6f5389f196f87344e2b5f35a2f0630117c644..f93e6c4c396f67836c8d1142000cc05682aac647 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js @@ -23,6 +23,7 @@ const ManuscriptHeader = ({ manuscriptType = {}, editorInChief = 'Unassigned', collection: { visibleStatus = 'Draft', customId, handlingEditor }, + isLatestVersion, }) => { const { authors = [], metadata = {}, submitted = null } = fragment const { title = 'No title', journal = '', type = '' } = metadata @@ -34,9 +35,13 @@ const ManuscriptHeader = ({ justify="space-between" > <H2 mb={1}>{title}</H2> - <Tag data-test-id="fragment-status" status> - {visibleStatus} - </Tag> + {isLatestVersion ? ( + <Tag data-test-id="fragment-status" status> + {visibleStatus} + </Tag> + ) : ( + <Tag oldStatus>Viewing an Older Version</Tag> + )} </Row> {authors.length > 0 && ( <Row diff --git a/packages/component-fixture-manager/src/fixtures/collectionIDs.js b/packages/component-fixture-manager/src/fixtures/collectionIDs.js index d4d27580eba01b480a3de9ec5650fc7a0dcc8b01..4f351142bab1ef0de0b4c1d82f767ed391b42b75 100644 --- a/packages/component-fixture-manager/src/fixtures/collectionIDs.js +++ b/packages/component-fixture-manager/src/fixtures/collectionIDs.js @@ -5,4 +5,5 @@ const chance = new Chance() module.exports = { standardCollID: chance.guid(), collectionReviewCompletedID: chance.guid(), + twoVersionsCollectionId: chance.guid(), } diff --git a/packages/component-fixture-manager/src/fixtures/collections.js b/packages/component-fixture-manager/src/fixtures/collections.js index 4984c9f078027f840f262f68a5fd22b914b87cbf..f6bb11aaa73ff13cf739b921e31910b2eaba35e5 100644 --- a/packages/component-fixture-manager/src/fixtures/collections.js +++ b/packages/component-fixture-manager/src/fixtures/collections.js @@ -4,12 +4,14 @@ const { fragment, reviewCompletedFragment } = require('./fragments') const { standardCollID, collectionReviewCompletedID, + twoVersionsCollectionId, } = require('./collectionIDs') const chance = new Chance() const collections = { collection: { id: standardCollID, + delete: jest.fn(), title: chance.sentence(), type: 'collection', fragments: [fragment.id], @@ -52,6 +54,7 @@ const collections = { }, collection1: { id: standardCollID, + delete: jest.fn(), title: chance.sentence(), type: 'collection', fragments: [fragment.id], @@ -94,6 +97,7 @@ const collections = { }, collectionReviewCompleted: { id: collectionReviewCompletedID, + delete: jest.fn(), type: 'collection', owners: [user.id], status: 'reviewCompleted', @@ -123,6 +127,36 @@ const collections = { }, technicalChecks: {}, }, + twoVersionsCollection: { + id: twoVersionsCollectionId, + title: chance.sentence(), + type: 'collection', + fragments: [fragment.id, reviewCompletedFragment.id], + owners: [user.id], + save: jest.fn(() => collections.collection), + 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 diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js index c8931dd9bb71a0259f0817a5a0a448ea53e37e42..5696e17019b802e9a131c762b184f058f2bc799e 100644 --- a/packages/component-fixture-manager/src/fixtures/fragments.js +++ b/packages/component-fixture-manager/src/fixtures/fragments.js @@ -18,6 +18,12 @@ const chance = new Chance() const fragments = { fragment: { id: chance.guid(), + delete: jest.fn(), + files: { + manuscripts: [{ id: chance.guid() }], + coverLetter: [], + supplementary: [], + }, collectionId: standardCollID, metadata: { title: chance.sentence(), @@ -180,6 +186,12 @@ const fragments = { }, noParentFragment: { id: chance.guid(), + delete: jest.fn(), + files: { + manuscripts: [{ id: chance.guid() }], + coverLetter: [], + supplementary: [], + }, collectionId: '', metadata: { title: chance.sentence(), @@ -199,9 +211,9 @@ const fragments = { }, reviewCompletedFragment: { id: chance.guid(), + delete: jest.fn(), type: 'fragment', files: { - coverLetter: [], manuscripts: [ { id: @@ -211,6 +223,7 @@ const fragments = { originalName: 'LinkedInProfileDemystified.pdf', }, ], + coverLetter: [], supplementary: [], }, owners: [user.id], diff --git a/packages/component-fixture-manager/src/fixtures/teams.js b/packages/component-fixture-manager/src/fixtures/teams.js index 5191421c22e3cd9f0f4c5256c18becd8c6d89166..c100c2a3798fc3bc4e635a98077806f11ad86bcc 100644 --- a/packages/component-fixture-manager/src/fixtures/teams.js +++ b/packages/component-fixture-manager/src/fixtures/teams.js @@ -33,6 +33,7 @@ const teams = { }, members: [handlingEditor.id], save: jest.fn(() => teams.heTeam), + delete: jest.fn(), updateProperties: jest.fn(() => teams.heTeam), id: heTeamID, }, @@ -49,6 +50,7 @@ const teams = { }, members: [reviewer.id, inactiveReviewer.id, answerReviewer.id], save: jest.fn(() => teams.revTeam), + delete: jest.fn(), updateProperties: jest.fn(() => teams.revTeam), id: revTeamID, }, @@ -65,6 +67,7 @@ const teams = { }, members: [reviewer.id, answerReviewer.id, recReviewer.id], save: jest.fn(() => teams.revRecommendationTeam), + delete: jest.fn(), updateProperties: jest.fn(() => teams.revRecommendationTeam), id: revRecommendationTeamID, }, @@ -81,6 +84,7 @@ const teams = { }, members: [submittingAuthor.id], save: jest.fn(() => teams.authorTeam), + delete: jest.fn(), updateProperties: jest.fn(() => teams.authorTeam), id: authorTeamID, }, diff --git a/packages/component-helper-service/src/services/Fragment.js b/packages/component-helper-service/src/services/Fragment.js index 174067efdfe0cbb7e7e7f627f5a8fd53884bd7f0..4b9e5edd4fefe8c3864698ba2ea205be4c3ecbac 100644 --- a/packages/component-helper-service/src/services/Fragment.js +++ b/packages/component-helper-service/src/services/Fragment.js @@ -1,4 +1,4 @@ -const get = require('lodash/get') +const { get, remove } = require('lodash') const User = require('./User') class Fragment { @@ -92,14 +92,14 @@ class Fragment { getInvitations({ isAccepted = true, role = 'reviewer', type }) { const { fragment: { invitations = [], recommendations = [] } } = this - let filteredInvitations = isAccepted + const filteredInvitations = isAccepted ? invitations.filter( inv => inv.role === role && inv.hasAnswer && inv.isAccepted, ) : invitations.filter(inv => inv.role === role && !inv.hasAnswer) if (type === 'submitted') { - filteredInvitations = filteredInvitations.filter(inv => + return filteredInvitations.filter(inv => recommendations.find( rec => rec.recommendationType === 'review' && @@ -108,12 +108,11 @@ class Fragment { ), ) } else if (type === 'accepted') { - filteredInvitations = filteredInvitations.filter(inv => - recommendations.find( - rec => - rec.recommendationType === 'review' && inv.userId !== rec.userId, - ), - ) + recommendations.forEach(rec => { + if (rec.recommendationType === 'review' && rec.submittedOn) { + remove(filteredInvitations, inv => inv.userId === rec.userId) + } + }) } return filteredInvitations diff --git a/packages/component-helper-service/src/services/Team.js b/packages/component-helper-service/src/services/Team.js index dd3050ebddd07abe3bf447ecea75676ca0ebd3e2..631bd09291af51bdacb90ec2978b374a1c672511 100644 --- a/packages/component-helper-service/src/services/Team.js +++ b/packages/component-helper-service/src/services/Team.js @@ -130,6 +130,15 @@ class Team { ) } + async getTeams(objectType) { + const { TeamModel, fragmentId, collectionId } = this + const objectId = objectType === 'collection' ? collectionId : fragmentId + const teams = await TeamModel.all() + return teams.filter( + team => team.object.type === objectType && team.object.id === objectId, + ) + } + async deleteHandlingEditor({ collection, role, user }) { const team = await this.getTeam({ role, diff --git a/packages/component-helper-service/src/tests/fragment.test.js b/packages/component-helper-service/src/tests/fragment.test.js index 3b5875eaf284e315ce1e3dd8a8792769e545e9c4..61e86748001df9b64466c7ed461bd1cf0c27c918 100644 --- a/packages/component-helper-service/src/tests/fragment.test.js +++ b/packages/component-helper-service/src/tests/fragment.test.js @@ -3,27 +3,238 @@ process.env.SUPPRESS_NO_CONFIG_WARNING = true const { cloneDeep } = require('lodash') const fixturesService = require('pubsweet-component-fixture-service') +const Chance = require('chance') +const chance = new Chance() const { fixtures } = fixturesService const { Fragment } = require('../Helper') +const acceptedReviewerId = chance.guid() +const submittedReviewerId1 = chance.guid() +const submittedReviewerId2 = chance.guid() +const fragment = { + invitations: [ + { + id: chance.guid(), + role: 'reviewer', + hasAnswer: true, + isAccepted: true, + userId: acceptedReviewerId, + invitedOn: chance.timestamp(), + respondedOn: chance.timestamp(), + type: 'invitation', + }, + { + id: chance.guid(), + role: 'reviewer', + hasAnswer: true, + isAccepted: true, + userId: submittedReviewerId1, + invitedOn: chance.timestamp(), + respondedOn: chance.timestamp(), + type: 'invitation', + }, + { + id: chance.guid(), + role: 'reviewer', + hasAnswer: true, + isAccepted: true, + userId: submittedReviewerId2, + invitedOn: chance.timestamp(), + respondedOn: chance.timestamp(), + type: 'invitation', + }, + ], + recommendations: [ + { + recommendation: 'publish', + recommendationType: 'review', + comments: [ + { + content: chance.paragraph(), + public: chance.bool(), + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: submittedReviewerId1, + createdOn: chance.timestamp(), + updatedOn: chance.timestamp(), + submittedOn: chance.timestamp(), + }, + { + recommendation: 'publish', + recommendationType: 'review', + comments: [ + { + content: chance.paragraph(), + public: chance.bool(), + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: submittedReviewerId2, + createdOn: chance.timestamp(), + updatedOn: chance.timestamp(), + submittedOn: chance.timestamp(), + }, + { + recommendation: 'publish', + recommendationType: 'review', + comments: [ + { + content: chance.paragraph(), + public: chance.bool(), + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: acceptedReviewerId, + createdOn: chance.timestamp(), + updatedOn: chance.timestamp(), + }, + ], +} + describe('Fragment helper', () => { let testFixtures = {} + let testFragment = {} beforeEach(() => { testFixtures = cloneDeep(fixtures) + testFragment = cloneDeep(fragment) }) - it('hasReviewReport - should return true if the fragment has a review report', () => { - const { fragment } = testFixtures.fragments - const fragmentHelper = new Fragment({ fragment }) + describe('hasReviewReport', () => { + it('should return true if the fragment has a review report', () => { + const { fragment } = testFixtures.fragments + const fragmentHelper = new Fragment({ fragment }) + + expect(fragmentHelper.hasReviewReport()).toBeTruthy() + }) + it('should return false if the fragment does not have a review report', () => { + const { fragment } = testFixtures.fragments + fragment.recommendations = [] + const fragmentHelper = new Fragment({ fragment }) - expect(fragmentHelper.hasReviewReport()).toBeTruthy() + expect(fragmentHelper.hasReviewReport()).toBeFalsy() + }) }) - it('hasReviewReport - should return false if the fragment does not have a review report', () => { - const { fragment } = testFixtures.fragments - fragment.recommendations = [] - const fragmentHelper = new Fragment({ fragment }) - expect(fragmentHelper.hasReviewReport()).toBeFalsy() + describe('getInvitations', () => { + it('should return accepted invitations if type is accepted and a review report has been started', () => { + const fragmentHelper = new Fragment({ fragment: testFragment }) + + const acceptedInvitations = fragmentHelper.getInvitations({ + isAccepted: true, + type: 'accepted', + }) + + expect(acceptedInvitations).toHaveLength(1) + }) + it('should return accepted invitations if type is accepted and no review report has been started', () => { + testFragment.recommendations = [ + { + recommendation: 'publish', + recommendationType: 'review', + comments: [ + { + content: chance.paragraph(), + public: chance.bool(), + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: submittedReviewerId1, + createdOn: chance.timestamp(), + updatedOn: chance.timestamp(), + submittedOn: chance.timestamp(), + }, + { + recommendation: 'publish', + recommendationType: 'review', + comments: [ + { + content: chance.paragraph(), + public: chance.bool(), + files: [ + { + id: chance.guid(), + name: 'file.pdf', + size: chance.natural(), + }, + ], + }, + ], + id: chance.guid(), + userId: submittedReviewerId2, + createdOn: chance.timestamp(), + updatedOn: chance.timestamp(), + submittedOn: chance.timestamp(), + }, + ] + + const fragmentHelper = new Fragment({ fragment: testFragment }) + + const acceptedInvitations = fragmentHelper.getInvitations({ + isAccepted: true, + type: 'accepted', + }) + + expect(acceptedInvitations).toHaveLength(1) + }) + it('should return invitations of submitted reviewers if type is submitted', () => { + const fragmentHelper = new Fragment({ fragment: testFragment }) + + const submittedInvitations = fragmentHelper.getInvitations({ + isAccepted: true, + type: 'submitted', + }) + + expect(submittedInvitations).toHaveLength(2) + }) + it('should return invitations of pending reviewers if type is pending and isAccepted is false', () => { + testFragment.invitations.push({ + id: chance.guid(), + role: 'reviewer', + hasAnswer: false, + isAccepted: false, + userId: chance.guid(), + invitedOn: chance.timestamp(), + respondedOn: chance.timestamp(), + type: 'invitation', + }) + const fragmentHelper = new Fragment({ fragment: testFragment }) + + const pendingInvitations = fragmentHelper.getInvitations({ + isAccepted: false, + type: 'pending', + }) + + expect(pendingInvitations).toHaveLength(1) + }) }) }) diff --git a/packages/component-invite/src/routes/fragmentsInvitations/post.js b/packages/component-invite/src/routes/fragmentsInvitations/post.js index 2eab3d2d1642e1aed6f2a9384bbf11b9058fd993..4442ae74c8b94459864bd373d114056d06dfd09c 100644 --- a/packages/component-invite/src/routes/fragmentsInvitations/post.js +++ b/packages/component-invite/src/routes/fragmentsInvitations/post.js @@ -11,6 +11,8 @@ const { const emailInvitations = require('./emails/invitations') +const { last } = require('lodash') + module.exports = models => async (req, res) => { const { email, role } = req.body @@ -40,6 +42,10 @@ module.exports = models => async (req, res) => { return res.status(400).json({ error: `Fragment ${fragmentId} does not match collection ${collectionId}.`, }) + if (last(collection.fragments) !== fragmentId) + return res.status(400).json({ + error: `Fragment ${fragmentId} is an older version.`, + }) fragment = await models.Fragment.find(fragmentId) } catch (e) { const notFoundError = await services.handleNotFoundError(e, 'item') diff --git a/packages/component-manuscript-manager/src/Collections.js b/packages/component-manuscript-manager/src/Collections.js index 8ce7c79779abb552a2728ad835b3e078d21ccaa8..81f8c4d7f6977ae496a3d021ac6dbc0a7be37dca 100644 --- a/packages/component-manuscript-manager/src/Collections.js +++ b/packages/component-manuscript-manager/src/Collections.js @@ -7,8 +7,8 @@ const Collections = app => { session: false, }) /** - * @api {get} /api/fragments Get latest fragments - * @apiGroup Fragments + * @api {get} /api/collections Get latest collections + * @apiGroup Collections * @apiSuccessExample {json} Success * HTTP/1.1 200 OK * [ @@ -28,7 +28,7 @@ const Collections = app => { * } * } * ] - * @apiErrorExample {json} Get fragments errors + * @apiErrorExample {json} Get collections errors * HTTP/1.1 403 Forbidden */ app.get( @@ -36,6 +36,20 @@ const Collections = app => { authBearer, require(`${routePath}/get`)(app.locals.models), ) + + /** + * @api {delete} /api/collections/:collectionId Delete collection with specified id + * @apiGroup Collections + * @apiSuccessExample {json} Success + * HTTP/1.1 204 Accepted + * @apiErrorExample {json} Get collections errors + * HTTP/1.1 403 Forbidden + */ + app.delete( + '/api/collections/:collectionId', + authBearer, + require(`${routePath}/delete`)(app.locals.models), + ) } module.exports = Collections diff --git a/packages/component-manuscript-manager/src/routes/collections/delete.js b/packages/component-manuscript-manager/src/routes/collections/delete.js new file mode 100644 index 0000000000000000000000000000000000000000..62ed1fc8c760f3f1c2aa0385e3a0db337dcb8be5 --- /dev/null +++ b/packages/component-manuscript-manager/src/routes/collections/delete.js @@ -0,0 +1,85 @@ +const { get, remove, concat, has } = require('lodash') +const config = require('config') + +const { + Team, + services, + authsome: authsomeHelper, +} = require('pubsweet-component-helper-service') + +const { + deleteFilesS3, +} = require('pubsweet-component-mts-package/src/PackageManager') + +const s3Config = get(config, 'pubsweet-component-aws-s3', {}) + +module.exports = models => async (req, res) => { + const { collectionId } = req.params + let collection, fragment + try { + collection = await models.Collection.find(collectionId) + + if (has(collection, 'status')) { + return res.status(400).json({ + error: 'You can only delete manuscripts while in draft.', + }) + } + + const fragmentId = collection.fragments[0] + fragment = await models.Fragment.find(fragmentId) + + const authsome = authsomeHelper.getAuthsome(models) + + const canDelete = await authsome.can(req.user, 'DELETE', collection) + if (!canDelete) + return res.status(403).json({ + error: 'Unauthorized.', + }) + + const teamHelper = new Team({ + TeamModel: models.Team, + fragmentId, + collectionId, + }) + const teams = await teamHelper.getTeams('fragment') + + await Promise.all( + teams.map(async team => { + await Promise.all( + team.members.map(async member => { + const user = await models.User.find(member) + + remove(user.teams, teamId => teamId === team.id) + + return user.save() + }), + ) + return team.delete() + }), + ) + + let fileKeys = concat( + fragment.files.manuscripts, + fragment.files.coverLetter, + fragment.files.supplementary, + fragmentId, + ) + + fileKeys = fileKeys.map(file => file.id) + + if (fileKeys.length > 1) { + await deleteFilesS3({ fileKeys, s3Config }) + } + + await fragment.delete() + + await collection.delete() + + return res.status(204).send() + } catch (e) { + const notFoundError = await services.handleNotFoundError(e, 'Item') + return res.status(notFoundError.status).json({ + error: notFoundError.message, + }) + } +} diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js index 55d54aa4274c9035da2fe92342ccab04929b6220..43206e0e19c9bb85290713f5eb829d04dd700df6 100644 --- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js +++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js @@ -1,5 +1,5 @@ const uuid = require('uuid') -const { pick, get, set, has, isEmpty } = require('lodash') +const { pick, get, set, has, isEmpty, last } = require('lodash') const config = require('config') const { v4 } = require('uuid') @@ -69,6 +69,15 @@ module.exports = models => async (req, res) => { error: 'Unauthorized.', }) + if ( + recommendationType === recommendations.type.editor && + last(collection.fragments) !== fragmentId + ) { + return res + .status(400) + .json({ error: 'Cannot make a recommendation on an older version.' }) + } + if ( recommendation === recommendations.publish && recommendationType === recommendations.type.editor && diff --git a/packages/component-manuscript-manager/src/tests/collections/delete.test.js b/packages/component-manuscript-manager/src/tests/collections/delete.test.js new file mode 100644 index 0000000000000000000000000000000000000000..cee28293a8b2143164d5697c09d49d50762200f5 --- /dev/null +++ b/packages/component-manuscript-manager/src/tests/collections/delete.test.js @@ -0,0 +1,65 @@ +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' +process.env.SUPPRESS_NO_CONFIG_WARNING = true + +const { cloneDeep } = require('lodash') +const fixturesService = require('pubsweet-component-fixture-service') +const requests = require('../requests') + +const { Model, fixtures } = fixturesService + +jest.mock('pubsweet-component-mts-package/src/PackageManager', () => ({ + deleteFilesS3: jest.fn(), +})) +jest.mock('@pubsweet/component-send-email', () => ({ + send: jest.fn(), +})) + +const path = '../routes/collections/delete' +const route = { + path: '/api/collections/:collectionId/', +} +describe('Delete collections route handler', () => { + let testFixtures = {} + let models + beforeEach(() => { + testFixtures = cloneDeep(fixtures) + models = Model.build(testFixtures) + }) + + it('should return an error when deleting a collection which is not in draft', async () => { + const { admin } = testFixtures.users + const { collection } = testFixtures.collections + + const res = await requests.sendRequest({ + userId: admin.id, + route, + models, + path, + params: { + collectionId: collection.id, + }, + }) + + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toBe('You can only delete manuscripts while in draft.') + }) + + it('should return success when deleting a draft collection', async () => { + const { admin } = testFixtures.users + const { collection } = testFixtures.collections + delete collection.status + + const res = await requests.sendRequest({ + userId: admin.id, + route, + models, + path, + params: { + collectionId: collection.id, + }, + }) + + expect(res.statusCode).toBe(204) + }) +}) diff --git a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js index b090a9cb6fc1ae464488560f7032b0ff97388dea..867d1ed0d05986fd03e389da064d08e4a19dfc77 100644 --- a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js +++ b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js @@ -357,4 +357,58 @@ describe('Post fragments recommendations route handler', () => { 'Cannot publish without at least one reviewer report.', ) }) + + it('should return an error when a HE makes a recommendation on an older version of a manuscript', async () => { + const { handlingEditor } = testFixtures.users + const { twoVersionsCollection } = testFixtures.collections + const { fragment } = testFixtures.fragments + body.recommendation = 'publish' + body.recommendationType = 'editorRecommendation' + + const res = await requests.sendRequest({ + body, + userId: handlingEditor.id, + models, + route, + path, + params: { + collectionId: twoVersionsCollection.id, + fragmentId: fragment.id, + }, + }) + + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + + expect(data.error).toEqual( + 'Cannot make a recommendation on an older version.', + ) + }) + + it('should return an error when an EiC makes a decision on an older version of a manuscript', async () => { + const { editorInChief } = testFixtures.users + const { twoVersionsCollection } = testFixtures.collections + const { fragment } = testFixtures.fragments + body.recommendation = 'publish' + body.recommendationType = 'editorRecommendation' + + const res = await requests.sendRequest({ + body, + userId: editorInChief.id, + models, + route, + path, + params: { + collectionId: twoVersionsCollection.id, + fragmentId: fragment.id, + }, + }) + + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + + expect(data.error).toEqual( + 'Cannot make a recommendation on an older version.', + ) + }) }) diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index ab97d296049c9240213c740f6987eb0cabd8f8ad..e04f5a7ddd225a507a3c9eb85b69de865aa08e0b 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -4,8 +4,9 @@ import { isEmpty, get } from 'lodash' import { Text, paddingHelper, - ReviewerDetails, AuthorReviews, + SubmitRevision, + ReviewerDetails, HERecommendation, ManuscriptHeader, ManuscriptAssignHE, @@ -13,7 +14,7 @@ import { ManuscriptDetailsTop, ResponseToInvitation, ManuscriptEicDecision, - SubmitRevision, + ResponseToRevisionRequest, } from 'pubsweet-component-faraday-ui' import ReviewerReportCard from './ReviewReportCard' @@ -49,17 +50,19 @@ const ManuscriptLayout = ({ heResponseExpanded, toggleReviewerResponse, invitationsWithReviewers, + responseToRevisionRequest, publonReviewers, reviewerResponseExpanded, pendingOwnRecommendation, toggleReviewerRecommendations, reviewerRecommendationExpanded, - shouldReview, + authorResponseToRevisonRequest, submittedOwnRecommendation, reviewerReports, reviewerRecommendations, toggleReviewerDetails, reviewerDetailsExpanded, + toggleResponseToRevisionRequest, editorialCommentsExpanded, toggleEditorialComments, submitRevision, @@ -67,6 +70,7 @@ const ManuscriptLayout = ({ recommendationHandler, inviteHandlingEditor, versions, + isLatestVersion, }) => ( <Root pb={30}> {!isEmpty(collection) && !isEmpty(fragment) ? ( @@ -87,6 +91,7 @@ const ManuscriptLayout = ({ handlingEditors={handlingEditors} inviteHE={toggleAssignHE} isFetching={isFetchingData.editorsFetching} + isLatestVersion={isLatestVersion} journal={journal} resendInvitation={inviteHandlingEditor.assignHE} revokeInvitation={inviteHandlingEditor.revokeHE} @@ -112,8 +117,10 @@ const ManuscriptLayout = ({ {get(currentUser, 'permissions.authorCanViewReportsDetails', false) && ( <AuthorReviews currentUser={currentUser} + getSignedUrl={getSignedUrl} journal={journal} reports={reviewerReports} + token={get(currentUser, 'token')} /> )} @@ -126,7 +133,16 @@ const ManuscriptLayout = ({ /> )} - {shouldReview && ( + {authorResponseToRevisonRequest.content && ( + <ResponseToRevisionRequest + authorReply={authorResponseToRevisonRequest.content} + expanded={responseToRevisionRequest} + fragment={fragment} + toggle={toggleResponseToRevisionRequest} + /> + )} + + {get(currentUser, 'permissions.canReview', false) && ( <ReviewerReportForm changeForm={changeForm} expanded={reviewerRecommendationExpanded} @@ -186,23 +202,26 @@ const ManuscriptLayout = ({ } invitations={invitationsWithReviewers} isFetching={isFetchingData.publonsFetching} + isLatestVersion={isLatestVersion} journal={journal} mb={2} publonReviewers={publonReviewers} reviewerReports={reviewerReports} scrollIntoView toggle={toggleReviewerDetails} + token={get(currentUser, 'token')} {...inviteReviewer} /> )} - {get(currentUser, 'permissions.canSubmitRevision', false) && ( - <SubmitRevision {...submitRevision} /> - )} + {isLatestVersion && + get(currentUser, 'permissions.canSubmitRevision', false) && ( + <SubmitRevision {...submitRevision} /> + )} - {get(currentUser, 'permissions.canMakeHERecommendation', false) && - (!invitationsWithReviewers.length || - reviewerRecommendations.length > 0) && ( + {isLatestVersion && + get(currentUser, 'permissions.canMakeHERecommendation', false) && + reviewerRecommendations.length > 0 && ( <HERecommendation formValues={get(formValues, 'editorialRecommendation', {})} hasReviewerReports={reviewerRecommendations.length > 0} @@ -214,16 +233,17 @@ const ManuscriptLayout = ({ /> )} - {get(currentUser, 'permissions.canMakeDecision', false) && ( - <ManuscriptEicDecision - collection={collection} - formValues={get(formValues, 'eicDecision')} - highlight={editorialRecommendations.length > 0} - messagesLabel={messagesLabel} - mt={2} - submitDecision={recommendationHandler.createRecommendation} - /> - )} + {isLatestVersion && + get(currentUser, 'permissions.canMakeDecision', false) && ( + <ManuscriptEicDecision + collection={collection} + formValues={get(formValues, 'eicDecision')} + highlight={editorialRecommendations.length > 0} + messagesLabel={messagesLabel} + mt={2} + submitDecision={recommendationHandler.createRecommendation} + /> + )} </Fragment> ) : ( <Text>Loading...</Text> diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index ab96f5c6b3bb159bf4e1d9f15ea20a341029e5a3..e51afe754d877bef7123805ae4f751fa935d5495 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -31,6 +31,7 @@ import { selectEditorialRecommendations, } from 'pubsweet-components-faraday/src/redux/recommendations' import { + canReview, getUserToken, currentUserIs, canViewReports, @@ -47,6 +48,7 @@ import { canViewReviewersDetails, canViewEditorialComments, pendingReviewerInvitation, + getFragmentAuthorResponse, canOverrideTechnicalChecks, authorCanViewReportsDetails, getOwnPendingRecommendation, @@ -72,6 +74,7 @@ import { redirectToError, parseSearchParams, getPublonsReviewers, + isLatestVersion, } from './utils' import { @@ -125,6 +128,10 @@ export default compose( state, match.params.version, ), + authorResponseToRevisonRequest: getFragmentAuthorResponse( + state, + match.params.version, + ), }), { changeForm, @@ -160,6 +167,7 @@ export default compose( isReviewer: currentUserIsReviewer(state, get(fragment, 'id', '')), isHEToManuscript: isHEToManuscript(state, get(collection, 'id', '')), permissions: { + canReview: canReview(state, collection, fragment), canSubmitRevision: canSubmitRevision(state, fragment), canMakeHERecommendation: canMakeHERecommendation(state, { collection, @@ -175,7 +183,7 @@ export default compose( canInviteReviewers: canInviteReviewers(state, collection), canMakeRecommendation: !isUndefined(pendingOwnRecommendation), canMakeRevision: canMakeRevision(state, collection, fragment), - canMakeDecision: canMakeDecision(state, collection, fragment), + canMakeDecision: canMakeDecision(state, collection), canEditManuscript: canEditManuscript(state, collection, fragment), canViewReviewersDetails: canViewReviewersDetails(state, collection), authorCanViewReportsDetails: authorCanViewReportsDetails( @@ -273,6 +281,10 @@ export default compose( toggleReviewerRecommendations: toggle, reviewerRecommendationExpanded: expanded, })), + fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({ + toggleResponseToRevisionRequest: toggle, + responseToRevisionRequest: expanded, + })), fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({ toggleReviewerDetails: toggle, reviewerDetailsExpanded: expanded, @@ -281,11 +293,9 @@ export default compose( toggleEditorialComments: toggle, editorialCommentsExpanded: expanded, })), - withProps(({ currentUser, collection, submittedOwnRecommendation }) => ({ + withProps(({ collection, fragment }) => ({ getSignedUrl, - shouldReview: - get(currentUser, 'isReviewer', false) && - isUndefined(submittedOwnRecommendation), + isLatestVersion: isLatestVersion(collection, fragment), })), withInviteHandlingEditor, withInviteReviewer, @@ -298,7 +308,6 @@ export default compose( history, setError, location, - shouldReview, reviewerReports, setEditorInChief, clearCustomError, @@ -306,12 +315,13 @@ export default compose( hasManuscriptFailure, fetchUpdatedCollection, editorialRecommendations, + fragment, currentUser: { isEIC, isInvitedHE, isInvitedToReview, isHEToManuscript, - permissions: { canInviteReviewers, canSubmitRevision }, + permissions: { canInviteReviewers, canSubmitRevision, canReview }, }, } = this.props @@ -346,14 +356,14 @@ export default compose( this.props.toggleReviewerResponse() } - if (shouldReview) { + if (canReview) { this.props.toggleReviewerRecommendations() } - if ( isHEToManuscript && !!reviewerReports.length && - editorialRecommendations.length === 0 + editorialRecommendations.length === 0 && + !fragment.responseToReviewers.content ) { this.props.toggleReviewerDetails() } @@ -365,6 +375,10 @@ export default compose( if (canSubmitRevision) { this.props.toggleEditorialComments() } + + if (get(fragment, 'responseToReviewers.content', false)) { + this.props.toggleResponseToRevisionRequest() + } }, componentDidUpdate(prevProps) { const { diff --git a/packages/component-manuscript/src/components/utils.js b/packages/component-manuscript/src/components/utils.js index e579d683636db3dfcb7917c961dd8d9f5a02eff8..f98db2189670e9dbb869bdc583cd6636ff3097e3 100644 --- a/packages/component-manuscript/src/components/utils.js +++ b/packages/component-manuscript/src/components/utils.js @@ -3,6 +3,7 @@ import { has, get, find, + last, omit, isEmpty, debounce, @@ -74,6 +75,9 @@ export const parseVersion = version => ({ abstract: get(version, 'metadata.abstract'), }) +export const isLatestVersion = (collection, fragment) => + get(fragment, 'id', '') === last(get(collection, 'fragments', [])) + export const parseJournalIssue = (journal, metadata) => journal.issueTypes.find(t => t.value === get(metadata, 'issue')) diff --git a/packages/component-manuscript/src/submitRevision/withSubmitRevision.js b/packages/component-manuscript/src/submitRevision/withSubmitRevision.js index 756551f82f57ab162c74915c3e47fca32701c4c5..57b8a4297065dd2efe876241e83e886a2b0eebbe 100644 --- a/packages/component-manuscript/src/submitRevision/withSubmitRevision.js +++ b/packages/component-manuscript/src/submitRevision/withSubmitRevision.js @@ -97,7 +97,7 @@ export default compose( }, deleteResponseFile: ({ setError, changeForm, setFetching }) => file => { setFetching(true) - return deleteFile(file.id, 'responseToReviewers') + return deleteFile({ fileId: file.id }, 'responseToReviewers') .then(r => { setFetching(false) changeForm('revision', 'responseToReviewers.file', null) diff --git a/packages/component-mts-package/src/PackageManager.js b/packages/component-mts-package/src/PackageManager.js index b98dbf10308c84b927b10d3f670b285a1b6ca8b7..cb9b8ea4c9fd644ef72c146706dde13348283cc4 100644 --- a/packages/component-mts-package/src/PackageManager.js +++ b/packages/component-mts-package/src/PackageManager.js @@ -115,6 +115,26 @@ const uploadFiles = async ({ filename, s3Config, config }) => { .catch(fileError(filename)) } +const deleteFilesS3 = async ({ fileKeys, s3Config }) => { + AWS.config.update({ + secretAccessKey: s3Config.secretAccessKey, + accessKeyId: s3Config.accessKeyId, + region: s3Config.region, + }) + const s3 = new AWS.S3() + + const params = { + Bucket: s3Config.bucket, + Delete: { + Objects: fileKeys.map(file => ({ Key: file })), + }, + } + + const deleteObjectsS3 = promisify(s3.deleteObjects.bind(s3)) + + return deleteObjectsS3(params) +} + const deleteFile = filename => { fs.access(filename, fs.constants.F_OK, err => { if (!err) { @@ -138,4 +158,5 @@ const uploadFTP = ({ filename, config }) => { module.exports = { createFilesPackage, uploadFiles, + deleteFilesS3, } diff --git a/packages/components-faraday/src/components/Dashboard/Dashboard.js b/packages/components-faraday/src/components/Dashboard/Dashboard.js index 0c106443fd6d2da18ebbdce662c5d738bc6b8dd8..a03dce5045de7b34967ca35b9bc7c8d7ec24180c 100644 --- a/packages/components-faraday/src/components/Dashboard/Dashboard.js +++ b/packages/components-faraday/src/components/Dashboard/Dashboard.js @@ -4,12 +4,13 @@ import { compose, withProps } from 'recompose' import { DashboardItems, DashboardFilters } from './' const Dashboard = ({ - deleteProject, + journal, + isFetching, dashboardItems, + deleteCollection, getFilterOptions, changeFilterValue, getDefaultFilterValue, - journal, }) => ( <Fragment> <DashboardFilters @@ -17,7 +18,11 @@ const Dashboard = ({ getDefaultFilterValue={getDefaultFilterValue} getFilterOptions={getFilterOptions} /> - <DashboardItems deleteProject={deleteProject} list={dashboardItems} /> + <DashboardItems + deleteCollection={deleteCollection} + isFetching={isFetching} + list={dashboardItems} + /> </Fragment> ) diff --git a/packages/components-faraday/src/components/Dashboard/DashboardItems.js b/packages/components-faraday/src/components/Dashboard/DashboardItems.js index f5a5e24df3fff52a255f0a70b427cec11726a782..72d16891c5a40b967a4f7e5491dd9180c05e186d 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardItems.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardItems.js @@ -5,9 +5,9 @@ import { connect } from 'react-redux' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' import { withRouter } from 'react-router-dom' -import { compose, setDisplayName, withHandlers, withProps } from 'recompose' import { ManuscriptCard, Row } from 'pubsweet-component-faraday-ui' import { canViewReports } from 'pubsweet-component-faraday-selectors' +import { compose, setDisplayName, withHandlers, withProps } from 'recompose' const DashboardItem = compose( connect((state, { collection }) => ({ @@ -18,7 +18,13 @@ const DashboardItem = compose( })), )(ManuscriptCard) -const DashboardItems = ({ list, onClick, deleteProject, canViewReports }) => ( +const DashboardItems = ({ + list, + onClick, + isFetching, + canViewReports, + deleteCollection, +}) => ( <Root data-test-id="dashboard-list-items"> {!list.length ? ( <Row justify="center" mt={4}> @@ -29,9 +35,10 @@ const DashboardItems = ({ list, onClick, deleteProject, canViewReports }) => ( <HideLoading key={collection.id}> <DashboardItem collection={collection} + isFetching={isFetching} key={collection.id} onClick={onClick} - onDelete={() => deleteProject(collection)} + onDelete={deleteCollection(collection)} /> </HideLoading> )) diff --git a/packages/components-faraday/src/components/Dashboard/DashboardPage.js b/packages/components-faraday/src/components/Dashboard/DashboardPage.js index eda5819b44ca04a7b91afc876a6cec42bba2569c..0cdd9b5ee5b05ba3c0a89a997c1f9fb3fb640344 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardPage.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardPage.js @@ -4,8 +4,9 @@ import { actions } from 'pubsweet-client' import { withJournal } from 'xpub-journal' import { ConnectPage } from 'xpub-connect' import { withRouter } from 'react-router-dom' -import { compose, withContext } from 'recompose' import { selectCurrentUser } from 'xpub-selectors' +import { compose, withHandlers, withContext } from 'recompose' +import { handleError, withFetching } from 'pubsweet-component-faraday-ui' import { getUserPermissions, @@ -32,13 +33,14 @@ export default compose( userPermissions, } }, - dispatch => ({ - deleteProject: collection => - dispatch(actions.deleteCollection(collection)), - }), + { + getCollections: actions.getCollections, + deleteCollection: actions.deleteCollection, + }, ), withRouter, withJournal, + withFetching, withFiltersHOC({ priority: priorityFilter, order: orderFilter, @@ -50,4 +52,25 @@ export default compose( }, ({ journal, currentUser }) => ({ journal, currentUser }), ), + withHandlers({ + deleteCollection: ({ + setFetching, + deleteCollection, + getCollections, + }) => collection => ({ hideModal, setModalError }) => { + setFetching(true) + deleteCollection(collection) + .then(() => { + setFetching(false) + hideModal() + getCollections() + }) + // again, the error is not being thrown from deleteCollection action and + // the catch is never run + .catch(err => { + setFetching(false) + handleError(setModalError)(err) + }) + }, + }), )(Dashboard) diff --git a/packages/xpub-faraday/config/authsome-helpers.js b/packages/xpub-faraday/config/authsome-helpers.js index c92571ecc8808a78710f8631b5fff43629b0d60b..500a6a103fc300331121a175465c9b18534487d3 100644 --- a/packages/xpub-faraday/config/authsome-helpers.js +++ b/packages/xpub-faraday/config/authsome-helpers.js @@ -1,65 +1,11 @@ -const { omit, get, last } = require('lodash') const config = require('config') const logger = require('@pubsweet/logger') +const { omit, get, last, chain } = require('lodash') const statuses = config.get('statuses') -const publicStatusesPermissions = ['author', 'reviewer'] const authorAllowedStatuses = ['revisionRequested', 'rejected', 'accepted'] -const parseAuthorsData = (coll, matchingCollPerm) => { - if (['reviewer'].includes(matchingCollPerm.permission)) { - coll.authors = coll.authors.map(a => omit(a, ['email'])) - } -} - -const setPublicStatuses = (coll, matchingCollPerm) => { - const status = get(coll, 'status', 'draft') - // coll.visibleStatus = statuses[status].public - if (publicStatusesPermissions.includes(matchingCollPerm.permission)) { - coll.visibleStatus = statuses[status].public - } -} - -const filterRefusedInvitations = (coll, user) => { - const matchingInv = coll.invitations.find(inv => inv.userId === user.id) - if (matchingInv === undefined) return null - if (matchingInv.hasAnswer === true && !matchingInv.isAccepted) return null - return coll -} - -const filterObjectData = ( - collectionsPermissions = [], - object = {}, - user = {}, -) => { - if (object.type === 'fragment') { - const matchingCollPerm = collectionsPermissions.find( - collPerm => object.id === collPerm.fragmentId, - ) - if (matchingCollPerm === undefined) return null - if (['reviewer'].includes(matchingCollPerm.permission)) { - object.files = omit(object.files, ['coverLetter']) - if (object.recommendations) - object.recommendations = object.recommendations.filter( - rec => rec.userId === user.id, - ) - } - parseAuthorsData(object, matchingCollPerm) - if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) { - return filterRefusedInvitations(object, user) - } - return object - } - const matchingCollPerm = collectionsPermissions.find( - collPerm => object.id === collPerm.id, - ) - if (matchingCollPerm === undefined) return null - setPublicStatuses(object, matchingCollPerm) - - return object -} - const getTeamsByPermissions = async ( teamIds = [], permissions = [], @@ -75,12 +21,6 @@ const getTeamsByPermissions = async ( }), )).filter(Boolean) -const heIsInvitedToFragment = async ({ user, Team, collectionId }) => - (await getTeamsByPermissions(user.teams, ['handlingEditor'], Team)).some( - // user is a member of the team with access to the fragment's parent collection - t => t.members.includes(user.id) && t.object.id === collectionId, - ) - const getUserPermissions = async ({ user, Team, @@ -119,12 +59,6 @@ const isHandlingEditor = ({ user, object }) => const isInDraft = fragment => !get(fragment, 'submitted') -const hasFragmentInDraft = async ({ object, Fragment }) => { - const lastFragmentId = last(get(object, 'fragments')) - const fragment = await Fragment.find(lastFragmentId) - return isInDraft(fragment) -} - const filterAuthorRecommendations = (recommendations, status, isLast) => { const canViewRecommendations = authorAllowedStatuses.includes(status) if (canViewRecommendations || !isLast) { @@ -236,70 +170,105 @@ const getCollections = async ({ user, models }) => { collection.visibleStatus = get(statuses, `${status}.${up.role}.label`) const role = get(up, 'role', 'author') - - let visibleStatus = get(statuses, `${status}.${role}.label`) - - if (role === 'reviewer' && status !== 'reviewersInvited') { - visibleStatus = await updateReviewerVisibleStatusByInvitation({ - collection, - FragmentModel: models.Fragment, - user, - }) - } - + const parsedStatuses = await setCollectionStatus({ + role, + user, + collection, + FragmentModel: models.Fragment, + }) return { ...collection, - visibleStatus, + ...parsedStatuses, } }), )).filter(Boolean) } -async function updateReviewerVisibleStatusByInvitation({ +const reviewerActionStatuses = [ + 'underReview', + 'reviewCompleted', + 'reviewersInvited', +] +const setCollectionStatus = async ({ + role, + user, collection, FragmentModel, - user, -}) { - const fragmentId = last(collection.fragments) - const fragment = await FragmentModel.find(fragmentId) - const invitation = get(fragment, 'invitations', []).find( - inv => inv.userId === user.id, - ) - const recommendationDone = - fragment.recommendations && - fragment.recommendations.some( - rec => - rec.recommendationType === 'review' && - rec.userId === user.id && - rec.submittedOn, - ) +}) => { + const status = get(collection, 'status', 'draft') + + if (role !== 'reviewer') { + return { + status, + visibleStatus: get(statuses, `${status}.${role}.label`), + } + } + + if (reviewerActionStatuses.includes(status)) { + const fragmentId = last(collection.fragments) + const fragment = await FragmentModel.find(fragmentId) + + const hasPendingInvitation = !chain(fragment) + .get('invitations', []) + .find(i => i.userId === user.id && !i.hasAnswer) + .isUndefined() + .value() + + if (hasPendingInvitation) { + return { + status: 'reviewersInvited', + visibleStatus: get(statuses, 'reviewersInvited.reviewer.label'), + } + } - if (recommendationDone) { - return get(statuses, `${collection.status}.reviewer.label`) + const hasSubmittedReport = !chain(fragment) + .get('recommendations', []) + .find( + r => + r.userId === user.id && + r.recommendationType === 'review' && + r.submittedOn, + ) + .isUndefined() + .value() + + if (hasSubmittedReport) { + return { + status: 'reviewCompleted', + visibleStatus: get(statuses, 'reviewCompleted.reviewer.label'), + } + } + + const hasAcceptedInvitation = !chain(fragment) + .get('invitations', []) + .find(i => i.userId === user.id && i.hasAnswer && i.isAccepted) + .isUndefined() + .value() + + if (hasAcceptedInvitation) { + return { + status: 'underReview', + visibleStatus: get(statuses, 'underReview.reviewer.label'), + } + } } - return get(invitation, 'hasAnswer', false) && - get(invitation, 'isAccepted', false) - ? get(statuses, `underReview.reviewer.label`) - : get(statuses, `reviewersInvited.reviewer.label`) + return { + status, + visibleStatus: get(statuses, `${status}.reviewer.label`), + } } module.exports = { - filterObjectData, - parseAuthorsData, - setPublicStatuses, - getTeamsByPermissions, - filterRefusedInvitations, isOwner, - isHandlingEditor, - getUserPermissions, - heIsInvitedToFragment, - hasPermissionForObject, isInDraft, - hasFragmentInDraft, - stripeFragmentByRole, - getUsersList, parseUser, + getUsersList, getCollections, - updateReviewerVisibleStatusByInvitation, + isHandlingEditor, + getUserPermissions, + setCollectionStatus, + stripeFragmentByRole, + getTeamsByPermissions, + hasPermissionForObject, } diff --git a/packages/xpub-faraday/config/authsome-mode.js b/packages/xpub-faraday/config/authsome-mode.js index 2bb207ebd4264f3d3210d226bb0134e25e0b1bff..67ab2326468688fca900b38bc23be699e69940d6 100644 --- a/packages/xpub-faraday/config/authsome-mode.js +++ b/packages/xpub-faraday/config/authsome-mode.js @@ -67,7 +67,6 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) { if (get(object, 'type') === 'collection') { return { filter: async collection => { - const status = get(collection, 'status', 'draft') const userPermissions = await helpers.getUserPermissions({ user, Team: context.models.Team, @@ -79,10 +78,6 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) { ) .map(up => up.objectId) - // if (collection.owners.map(o => o.id).includes(user.id)) { - // return collection - // } - // TODO: check why collPermission can be undefined const collPermission = userPermissions.find( p => p.objectId === collection.id || @@ -90,27 +85,22 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) { ) const role = get(collPermission, 'role', 'author') - let visibleStatus = get(statuses, `${status}.${role}.label`) - - if (role === 'reviewer' && status !== 'reviewersInvited') { - visibleStatus = await helpers.updateReviewerVisibleStatusByInvitation( - { - collection, - FragmentModel: context.models.Fragment, - user, - }, - ) - } + const parsedStatuses = await helpers.setCollectionStatus({ + role, + user, + collection, + FragmentModel: context.models.Fragment, + }) return { ...collection, + ...parsedStatuses, fragments: role !== 'reviewer' ? collection.fragments : collection.fragments.filter(fragId => fragmentPermissions.includes(fragId), ), - visibleStatus, } }, } diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js index cc116eecca70a91cb64c085c77fc0c6b6da39cfd..60cd146dec55f085d5535fc0eb934edfcfebc7da 100644 --- a/packages/xpub-faraday/config/validations.js +++ b/packages/xpub-faraday/config/validations.js @@ -98,6 +98,7 @@ module.exports = { size: Joi.number(), url: Joi.string(), signedUrl: Joi.string(), + submittedOn: Joi.date(), }), ), }), diff --git a/packages/xpub-faraday/tests/config/authsome-helpers.test.js b/packages/xpub-faraday/tests/config/authsome-helpers.test.js index 0e51ce6aed419d58cda2bf016f2bbc09d821e8b5..a15e1f9852337441114611dcc2a0e426bf904c5c 100644 --- a/packages/xpub-faraday/tests/config/authsome-helpers.test.js +++ b/packages/xpub-faraday/tests/config/authsome-helpers.test.js @@ -195,42 +195,4 @@ describe('Authsome Helpers', () => { expect(parsedUser).toHaveProperty('email') expect(parsedUser).toHaveProperty('username') }) - describe('updateReviewerVisibleStatusByInvitation', () => { - it('should return the fragment status for reviewer when they have done the review', async () => { - const { answerReviewer } = testFixtures.users - const { collectionReviewCompleted } = testFixtures.collections - - const visibleStatus = await ah.updateReviewerVisibleStatusByInvitation({ - collection: collectionReviewCompleted, - FragmentModel: models.Fragment, - user: answerReviewer, - }) - - expect(visibleStatus).toEqual('Review Completed') - }) - it('should return the underReview status for reviewer when they have accepted the review', async () => { - const { reviewer } = testFixtures.users - const { collectionReviewCompleted } = testFixtures.collections - - const visibleStatus = await ah.updateReviewerVisibleStatusByInvitation({ - collection: collectionReviewCompleted, - FragmentModel: models.Fragment, - user: reviewer, - }) - - expect(visibleStatus).toEqual('Complete Review') - }) - it('should return the reviewersInvited status for reviewer when they have to accept the review', async () => { - const { recReviewer } = testFixtures.users - const { collectionReviewCompleted } = testFixtures.collections - - const visibleStatus = await ah.updateReviewerVisibleStatusByInvitation({ - collection: collectionReviewCompleted, - FragmentModel: models.Fragment, - user: recReviewer, - }) - - expect(visibleStatus).toEqual('Respond to Invite') - }) - }) })