diff --git a/packages/component-faraday-ui/src/ReviewersTable.js b/packages/component-faraday-ui/src/ReviewersTable.js index 9f9e2aaa75309afd25084e94a65cbcf65e2c646b..e2197d20c406eeff827b6b4051f48062d7e7493e 100644 --- a/packages/component-faraday-ui/src/ReviewersTable.js +++ b/packages/component-faraday-ui/src/ReviewersTable.js @@ -41,9 +41,9 @@ const ReviewersTable = ({ invitation, 'person.lastName', )}`}</Text> - {invitation.isAccepted && ( + {invitation.reviewerNumber && ( <Text customId ml={1}> - {renderAcceptedLabel(index)} + Reviewer {invitation.reviewerNumber} </Text> )} </td> @@ -102,12 +102,7 @@ export default compose( withProps(({ invitations = [] }) => ({ invitations: orderBy(invitations, orderInvitations), })), - withProps(({ invitations = [] }) => ({ - firstAccepted: invitations.findIndex(i => i.hasAnswer && i.isAccepted), - })), withHandlers({ - renderAcceptedLabel: ({ firstAccepted, invitations }) => index => - `Reviewer ${index - firstAccepted + 1}`, getInvitationStatus: () => ({ hasAnswer, isAccepted }) => { if (!hasAnswer) return 'PENDING' if (isAccepted) return 'ACCEPTED' diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js index d8a41b821abe0639bbb6f9cfbe6ffe17b9f75189..d1a9cbcffcb1e50ee2f2eda2aab33b283e9a8557 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js @@ -13,6 +13,7 @@ import { ContextualBox, ReviewersTable, PublonsTable, + indexReviewers, ReviewerReport, InviteReviewers, ReviewerBreakdown, @@ -118,14 +119,14 @@ const ReviewerDetails = ({ {reports.length === 0 && ( <Text align="center">No reports submitted yet.</Text> )} - {reports.map((report, index) => ( + {reports.map(report => ( <ReviewerReport journal={journal} key={report.id} onDownload={downloadFile} onPreview={previewFile} report={report} - reviewerIndex={index + 1} + reviewerIndex={report.reviewerNumber} showOwner /> ))} @@ -154,7 +155,10 @@ export default compose( ...i, review: reviewerReports.find(r => r.userId === i.userId), })), - reports: reviewerReports.filter(r => r.submittedOn), + reports: indexReviewers( + reviewerReports.filter(r => r.submittedOn), + invitations, + ), }), ), withProps(({ currentUser }) => ({ diff --git a/packages/component-faraday-ui/src/helpers/utils.js b/packages/component-faraday-ui/src/helpers/utils.js index 898fd3ebc62282763a1331fd359f9d8da85a732c..8b798e34e7b73272c922ab6201019b5b4b221fb9 100644 --- a/packages/component-faraday-ui/src/helpers/utils.js +++ b/packages/component-faraday-ui/src/helpers/utils.js @@ -1,4 +1,4 @@ -import { get, chain } from 'lodash' +import { get, chain, find } from 'lodash' export const handleError = fn => e => { fn(get(JSON.parse(e.response), 'error', 'Oops! Something went wrong!')) @@ -10,3 +10,14 @@ export const getReportComments = ({ report, isPublic = false }) => .find(c => c.public === isPublic) .get('content') .value() + +export const indexReviewers = (reports = [], invitations = []) => { + reports.forEach(report => { + report.reviewerNumber = get( + find(invitations, ['userId', report.userId]), + 'reviewerNumber', + 0, + ) + }) + return reports +} diff --git a/packages/component-fixture-manager/src/fixtures/collectionIDs.js b/packages/component-fixture-manager/src/fixtures/collectionIDs.js index e66bb71a232758a7de0746033ba2493745157250..a964c2c498c452599e68dd3457a24eac4c0a3b49 100644 --- a/packages/component-fixture-manager/src/fixtures/collectionIDs.js +++ b/packages/component-fixture-manager/src/fixtures/collectionIDs.js @@ -7,5 +7,6 @@ module.exports = { collectionReviewCompletedID: chance.guid(), collectionNoInvitesID: chance.guid(), twoVersionsCollectionId: chance.guid(), + oneReviewedFragmentCollectionID: chance.guid(), noEditorRecomedationCollectionID: chance.guid(), } diff --git a/packages/component-fixture-manager/src/fixtures/collections.js b/packages/component-fixture-manager/src/fixtures/collections.js index 7d1df81e4f6379f9bb4d21bc243a2ed97a9e9e2d..0fbff658ca84fc9266a31674461679e769a1ff13 100644 --- a/packages/component-fixture-manager/src/fixtures/collections.js +++ b/packages/component-fixture-manager/src/fixtures/collections.js @@ -17,6 +17,7 @@ const { collectionReviewCompletedID, collectionNoInvitesID, twoVersionsCollectionId, + oneReviewedFragmentCollectionID, noEditorRecomedationCollectionID, } = require('./collectionIDs') @@ -30,6 +31,7 @@ const collections = { fragments: [fragment.id], owners: [user.id], save: jest.fn(() => collections.collection), + getFragments: jest.fn(() => [fragment]), invitations: [ { id: chance.guid(), @@ -73,6 +75,7 @@ const collections = { fragments: [fragment.id], owners: [user.id], save: jest.fn(() => collections.collection), + getFragments: jest.fn(() => [fragment]), invitations: [ { id: chance.guid(), @@ -115,6 +118,7 @@ const collections = { fragments: [fragment1.id, noInvitesFragment.id], owners: [user.id], save: jest.fn(() => collections.collection2), + getFragments: jest.fn(() => [fragment1, noInvitesFragment]), invitations: [ { id: chance.guid(), @@ -159,6 +163,7 @@ const collections = { created: chance.timestamp(), customId: '0000001', fragments: [reviewCompletedFragment.id], + getFragments: jest.fn(() => [reviewCompletedFragment]), invitations: [ { id: chance.guid(), @@ -189,6 +194,7 @@ const collections = { fragments: [fragment.id, reviewCompletedFragment.id], owners: [user.id], save: jest.fn(() => collections.collection), + getFragments: jest.fn(() => [fragment, reviewCompletedFragment]), invitations: [ { id: chance.guid(), @@ -219,6 +225,7 @@ const collections = { fragments: [], owners: [user.id], save: jest.fn(() => collections.collection), + getFragments: jest.fn(() => []), customId: chance.natural({ min: 999999, max: 9999999 }), }, noEditorRecomedationCollection: { @@ -228,6 +235,7 @@ const collections = { fragments: [noEditorRecomedationFragment.id], owners: [user.id], save: jest.fn(() => collections.noEditorRecomedationCollection), + getFragments: jest.fn(() => [noEditorRecomedationFragment]), invitations: [ { id: chance.guid(), @@ -263,6 +271,37 @@ const collections = { }, status: 'reviewCompleted', }, + oneReviewedFragmentCollection: { + id: oneReviewedFragmentCollectionID, + title: chance.sentence(), + type: 'collection', + fragments: [reviewCompletedFragment.id, noInvitesFragment.id], + owners: [user.id], + save: jest.fn(() => collections.collection), + getFragments: jest.fn(() => [reviewCompletedFragment, noInvitesFragment]), + invitations: [ + { + id: chance.guid(), + role: 'handlingEditor', + hasAnswer: true, + isAccepted: false, + userId: handlingEditor.id, + invitedOn: chance.timestamp(), + respondedOn: null, + }, + ], + handlingEditor: { + id: handlingEditor.id, + hasAnswer: false, + isAccepted: false, + email: handlingEditor.email, + invitedOn: chance.timestamp(), + respondedOn: null, + name: `${handlingEditor.firstName} ${handlingEditor.lastName}`, + }, + status: 'revisionRequested', + customId: chance.natural({ min: 999999, max: 9999999 }), + }, } module.exports = collections diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js index 15e4cb3fa981f4316d1d686a6dc2414d783ed89b..337c0d943c7e6fd87e47fd3e31ff3458c0fb567d 100644 --- a/packages/component-fixture-manager/src/fixtures/fragments.js +++ b/packages/component-fixture-manager/src/fixtures/fragments.js @@ -340,6 +340,7 @@ const fragments = { invitedOn: chance.timestamp(), isAccepted: true, respondedOn: chance.timestamp(), + reviewerNumber: 2, }, { id: chance.guid(), diff --git a/packages/component-helper-service/src/services/Collection.js b/packages/component-helper-service/src/services/Collection.js index 6e05d9a584f61eddc5a63a7ad309ac81bea1edb6..0db955323c5e15fe008bf2ef90152788d078df46 100644 --- a/packages/component-helper-service/src/services/Collection.js +++ b/packages/component-helper-service/src/services/Collection.js @@ -1,4 +1,4 @@ -const { findLast, get } = require('lodash') +const { findLast, isEmpty, maxBy, get, flatMap } = require('lodash') const Fragment = require('./Fragment') @@ -109,6 +109,27 @@ class Collection { return lastName || firstName } + async getReviewerNumber({ userId }) { + const allCollectionFragments = await this.collection.getFragments() + const allCollectionInvitations = flatMap( + allCollectionFragments, + fragment => fragment.invitations, + ) + const allNumberedInvitationsForUser = allCollectionInvitations + .filter(invite => invite.userId === userId) + .filter(invite => invite.reviewerNumber) + + if (isEmpty(allNumberedInvitationsForUser)) { + const maxReviewerNumber = get( + maxBy(allCollectionInvitations, 'reviewerNumber'), + 'reviewerNumber', + 0, + ) + return maxReviewerNumber + 1 + } + return allNumberedInvitationsForUser[0].reviewerNumber + } + // eslint-disable-next-line class-methods-use-this hasAtLeastOneReviewReport(fragments) { return fragments.some(fragment => diff --git a/packages/component-helper-service/src/tests/collection.test.js b/packages/component-helper-service/src/tests/collection.test.js index 41c9f0ed6481079f790df50fc83584af62f2d1c4..167153af8c03fc7149c36a9e8c149de351c0f7db 100644 --- a/packages/component-helper-service/src/tests/collection.test.js +++ b/packages/component-helper-service/src/tests/collection.test.js @@ -10,11 +10,65 @@ const { Collection, Fragment } = require('../Helper') describe('Collection helper', () => { let testFixtures = {} let models + beforeEach(() => { testFixtures = cloneDeep(fixtures) models = Model.build(testFixtures) }) + describe('getReviewerNumber', () => { + it('should assign reviewer number 1 on invitation if no other reviewer numbers exist', async () => { + const { collection } = testFixtures.collections + const { reviewer } = testFixtures.users + const collectionHelper = new Collection({ collection }) + + const reviewerNumber = await collectionHelper.getReviewerNumber({ + userId: reviewer.id, + }) + + expect(reviewerNumber).toBe(1) + }) + it('should assign next reviewer number on invitation if another reviewer numbers exist', async () => { + const { collectionReviewCompleted } = testFixtures.collections + const { reviewer } = testFixtures.users + const collectionHelper = new Collection({ + collection: collectionReviewCompleted, + }) + + const reviewerNumber = await collectionHelper.getReviewerNumber({ + userId: reviewer.id, + }) + + expect(reviewerNumber).toBe(3) + }) + it('should keep reviewer number across fragment versions', async () => { + const { oneReviewedFragmentCollection } = testFixtures.collections + const { answerReviewer } = testFixtures.users + const collectionHelper = new Collection({ + collection: oneReviewedFragmentCollection, + }) + + const reviewerNumber = await collectionHelper.getReviewerNumber({ + userId: answerReviewer.id, + }) + + expect(reviewerNumber).toBe(2) + }) + it('should assign next reviewer number across fragment versions', async () => { + const { oneReviewedFragmentCollection } = testFixtures.collections + const { reviewer } = testFixtures.users + const collectionHelper = new Collection({ + collection: oneReviewedFragmentCollection, + }) + + const reviewerNumber = await collectionHelper.getReviewerNumber({ + userId: reviewer.id, + }) + + expect(reviewerNumber).toBe(3) + }) + }) + describe('hasAtLeastOneReviewReport', () => { it('should return true if collection has at least one report from reviewers.', async () => { const { collection } = testFixtures.collections diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js index 7846722ed2f83db255a59ff80f95c142ccf2d3ec..7cd5b2ae49984d23fff26a53ae1bfb38a19f8244 100644 --- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js +++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js @@ -1,3 +1,4 @@ +const { find } = require('lodash') const { services, authsome: authsomeHelper, @@ -8,6 +9,7 @@ const Notification = require('../../notifications/notification') module.exports = models => async (req, res) => { const { collectionId, fragmentId, recommendationId } = req.params + const userId = req.user let collection, fragment try { collection = await models.Collection.find(collectionId) @@ -25,7 +27,7 @@ module.exports = models => async (req, res) => { if (!recommendation) return res.status(404).json({ error: 'Recommendation not found.' }) - if (recommendation.userId !== req.user) + if (recommendation.userId !== userId) return res.status(403).json({ error: 'Unauthorized.', }) @@ -35,14 +37,14 @@ module.exports = models => async (req, res) => { fragment, path: req.route.path, } - const canPatch = await authsome.can(req.user, 'PATCH', target) + const canPatch = await authsome.can(userId, 'PATCH', target) if (!canPatch) return res.status(403).json({ error: 'Unauthorized.', }) const UserModel = models.User - const reviewer = await UserModel.find(req.user) + const reviewer = await UserModel.find(userId) Object.assign(recommendation, req.body) recommendation.updatedOn = Date.now() @@ -62,6 +64,16 @@ module.exports = models => async (req, res) => { const collectionHelper = new Collection({ collection }) collectionHelper.updateStatus({ newStatus: 'reviewCompleted' }) } + + const collectionHelper = new Collection({ collection }) + const reviewerNumber = await collectionHelper.getReviewerNumber({ + userId, + }) + + find(fragment.invitations, [ + 'userId', + userId, + ]).reviewerNumber = reviewerNumber } fragment.save()