diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 29d4297a301a196af06487e9e5fbb789e08ae9ae..f265fd9dc0b366803c2eac1c74d5772e6ba79269 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -370,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`, []) 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/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 189c88776c22673d835c0639b5889973008a39ec..fd6ce0316577b29f1ca95619cdd180a860209c54 100644 --- a/packages/component-faraday-ui/src/Tag.js +++ b/packages/component-faraday-ui/src/Tag.js @@ -7,10 +7,11 @@ import { marginHelper } from './styledHelpers' const tagCSS = props => { if (has(props, 'oldStatus')) { return css` - background-color: ${th('tag.statusBackgroundColor')}; - font-size: 85%; - height: 24px; - padding: calc(${th('gridUnit')} / 4) ${th('gridUnit')}; + background-color: ${th('colorFurnitureHue')}; + height: calc(${th('gridUnit')} * 3) + font-weight: ${th('tag.fontWeight')}; + padding: calc(${th('gridUnit')} / 2) ${th('gridUnit')} 0px + ${th('gridUnit')}; ` } 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/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-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-manuscript-manager/src/routes/collections/delete.js b/packages/component-manuscript-manager/src/routes/collections/delete.js index 05b9e042f9c08c24f9367aea7f6db546e08e8b08..62ed1fc8c760f3f1c2aa0385e3a0db337dcb8be5 100644 --- a/packages/component-manuscript-manager/src/routes/collections/delete.js +++ b/packages/component-manuscript-manager/src/routes/collections/delete.js @@ -1,4 +1,4 @@ -const { get, remove, concat } = require('lodash') +const { get, remove, concat, has } = require('lodash') const config = require('config') const { @@ -19,6 +19,12 @@ module.exports = models => async (req, res) => { 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) @@ -48,17 +54,18 @@ module.exports = models => async (req, res) => { return user.save() }), ) - return team.delete() }), ) - const fileKeys = concat( + let fileKeys = concat( fragment.files.manuscripts, fragment.files.coverLetter, fragment.files.supplementary, fragmentId, - ).map(file => file.id) + ) + + fileKeys = fileKeys.map(file => file.id) if (fileKeys.length > 1) { await deleteFilesS3({ fileKeys, s3Config }) 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 9f3c7710eadbbc37d3e96204394bcae5f50effe9..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,16 +50,19 @@ const ManuscriptLayout = ({ heResponseExpanded, toggleReviewerResponse, invitationsWithReviewers, + responseToRevisionRequest, publonReviewers, reviewerResponseExpanded, pendingOwnRecommendation, toggleReviewerRecommendations, reviewerRecommendationExpanded, + authorResponseToRevisonRequest, submittedOwnRecommendation, reviewerReports, reviewerRecommendations, toggleReviewerDetails, reviewerDetailsExpanded, + toggleResponseToRevisionRequest, editorialCommentsExpanded, toggleEditorialComments, submitRevision, @@ -113,8 +117,10 @@ const ManuscriptLayout = ({ {get(currentUser, 'permissions.authorCanViewReportsDetails', false) && ( <AuthorReviews currentUser={currentUser} + getSignedUrl={getSignedUrl} journal={journal} reports={reviewerReports} + token={get(currentUser, 'token')} /> )} @@ -127,6 +133,15 @@ const ManuscriptLayout = ({ /> )} + {authorResponseToRevisonRequest.content && ( + <ResponseToRevisionRequest + authorReply={authorResponseToRevisonRequest.content} + expanded={responseToRevisionRequest} + fragment={fragment} + toggle={toggleResponseToRevisionRequest} + /> + )} + {get(currentUser, 'permissions.canReview', false) && ( <ReviewerReportForm changeForm={changeForm} @@ -194,6 +209,7 @@ const ManuscriptLayout = ({ reviewerReports={reviewerReports} scrollIntoView toggle={toggleReviewerDetails} + token={get(currentUser, 'token')} {...inviteReviewer} /> )} diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index 82b4dc320d585a44e7fbe85a5aaad57fd4a31429..e51afe754d877bef7123805ae4f751fa935d5495 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -48,6 +48,7 @@ import { canViewReviewersDetails, canViewEditorialComments, pendingReviewerInvitation, + getFragmentAuthorResponse, canOverrideTechnicalChecks, authorCanViewReportsDetails, getOwnPendingRecommendation, @@ -127,6 +128,10 @@ export default compose( state, match.params.version, ), + authorResponseToRevisonRequest: getFragmentAuthorResponse( + state, + match.params.version, + ), }), { changeForm, @@ -276,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, @@ -306,6 +315,7 @@ export default compose( hasManuscriptFailure, fetchUpdatedCollection, editorialRecommendations, + fragment, currentUser: { isEIC, isInvitedHE, @@ -349,11 +359,11 @@ export default compose( 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/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/components-faraday/src/components/Dashboard/DashboardPage.js b/packages/components-faraday/src/components/Dashboard/DashboardPage.js index 2aadccce522ce995b4387b64b0f1b891433befe9..0cdd9b5ee5b05ba3c0a89a997c1f9fb3fb640344 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardPage.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardPage.js @@ -34,6 +34,7 @@ export default compose( } }, { + getCollections: actions.getCollections, deleteCollection: actions.deleteCollection, }, ), @@ -52,15 +53,17 @@ export default compose( ({ journal, currentUser }) => ({ journal, currentUser }), ), withHandlers({ - deleteCollection: ({ setFetching, deleteCollection }) => collection => ({ - hideModal, - setModalError, - }) => { + 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 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(), }), ), }),