Skip to content
Snippets Groups Projects
Commit 523738e0 authored by Sebastian Mihalache's avatar Sebastian Mihalache :hammer_pick:
Browse files

Merge branch 'submit-revision' into 'develop'

Submit revision

See merge request !11
parents e8d56eef c401b294
No related branches found
No related tags found
2 merge requests!13Sprint #14,!11Submit revision
Showing
with 385 additions and 86 deletions
......@@ -56,10 +56,11 @@ export const getHERecommendation = (state, collectionId, fragmentId) => {
)
}
const cantMakeDecisionStatuses = ['rejected', 'published', 'draft']
export const canMakeDecision = (state, collection) => {
const status = get(collection, 'status')
if (!status || status === 'rejected' || status === 'published') return false
if (!status || cantMakeDecisionStatuses.includes(status)) return false
const isEIC = currentUserIs(state, 'adminEiC')
return isEIC && status
......@@ -72,3 +73,11 @@ export const canSeeReviewersReports = (state, collectionId) => {
}
export const canSeeEditorialComments = canSeeReviewersReports
export const canMakeRevision = (state, collection) => {
const currentUserId = get(state, 'currentUser.user.id')
return (
collection.status === 'revisionRequested' &&
collection.owners.map(o => o.id).includes(currentUserId)
)
}
_build/
api/
logs/
node_modules/
uploads/
.env.*
.env
config/local*.*
\ No newline at end of file
# Helper Service
module.exports = require('./src/Fixture')
{
"name": "pubsweet-component-fixture-service",
"version": "0.0.1",
"description": "fixture service component for pubsweet",
"license": "MIT",
"author": "Collaborative Knowledge Foundation",
"files": [
"src"
],
"main": "index.js",
"repository": {
"type": "git",
"url": "https://gitlab.coko.foundation/xpub/xpub-faraday",
"path": "component-fixture-service"
},
"dependencies": {
},
"peerDependencies": {
"@pubsweet/logger": "^0.0.1",
"pubsweet-server": "^1.0.1"
},
"publishConfig": {
"access": "public"
}
}
const fixtures = require('./fixtures/fixtures')
const Model = require('./helpers/Model')
module.exports = {
fixtures,
Model,
}
const Chance = require('chance')
const chance = new Chance()
const heID = chance.guid()
const revId = chance.guid()
const collId = chance.guid()
module.exports = {
heTeamID: heID,
revTeamID: revId,
standardCollID: collId,
}
const Chance = require('chance')
const {
user,
handlingEditor,
author,
reviewer,
answerReviewer,
} = require('./userData')
const { user, handlingEditor, answerHE } = require('./userData')
const { fragment } = require('./fragments')
const { standardCollID } = require('./collectionIDs')
const chance = new Chance()
const collections = {
collection: {
id: chance.guid(),
id: standardCollID,
title: chance.sentence(),
type: 'collection',
fragments: [fragment.id],
owners: [user.id],
save: jest.fn(),
authors: [
{
userId: author.id,
isSubmitting: true,
isCorresponding: false,
},
],
invitations: [
{
id: chance.guid(),
......@@ -36,19 +24,10 @@ const collections = {
},
{
id: chance.guid(),
role: 'reviewer',
hasAnswer: false,
isAccepted: false,
userId: reviewer.id,
invitedOn: chance.timestamp(),
respondedOn: null,
},
{
id: chance.guid(),
role: 'reviewer',
role: 'handlingEditor',
hasAnswer: true,
isAccepted: false,
userId: answerReviewer.id,
userId: answerHE.id,
invitedOn: chance.timestamp(),
respondedOn: chance.timestamp(),
},
......
const Chance = require('chance')
const {
user,
handlingEditor,
author,
submittingAuthor,
reviewer,
answerReviewer,
recReviewer,
handlingEditor,
admin,
} = require('./userData')
const { fragment } = require('./fragments')
const { standardCollID } = require('./collectionIDs')
const { user } = require('./userData')
const chance = new Chance()
const collections = {
collection: {
const fragments = {
fragment: {
id: chance.guid(),
title: chance.sentence(),
type: 'collection',
fragments: [fragment.id],
owners: [user.id],
save: jest.fn(),
collectionId: standardCollID,
metadata: {
title: chance.sentence(),
abstract: chance.paragraph(),
},
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: recReviewer.id,
createdOn: chance.timestamp(),
updatedOn: chance.timestamp(),
},
{
recommendation: 'minor',
recommendationType: 'editorRecommendation',
comments: [
{
content: chance.paragraph(),
public: chance.bool(),
files: [
{
id: chance.guid(),
name: 'file.pdf',
size: chance.natural(),
},
],
},
],
id: chance.guid(),
userId: handlingEditor.id,
createdOn: chance.timestamp(),
updatedOn: chance.timestamp(),
},
{
recommendation: 'publish',
recommendationType: 'editorRecommendation',
comments: [
{
content: chance.paragraph(),
public: chance.bool(),
files: [
{
id: chance.guid(),
name: 'file.pdf',
size: chance.natural(),
},
],
},
],
id: chance.guid(),
userId: admin.id,
createdOn: chance.timestamp(),
updatedOn: chance.timestamp(),
},
],
authors: [
{
userId: author.id,
id: submittingAuthor.id,
isSubmitting: true,
isCorresponding: false,
},
],
invitations: [
{
id: chance.guid(),
role: 'handlingEditor',
hasAnswer: false,
isAccepted: false,
userId: handlingEditor.id,
invitedOn: chance.timestamp(),
respondedOn: null,
},
{
id: chance.guid(),
role: 'reviewer',
......@@ -53,16 +111,9 @@ const collections = {
respondedOn: chance.timestamp(),
},
],
handlingEditor: {
id: handlingEditor.id,
hasAnswer: false,
isAccepted: false,
email: handlingEditor.email,
invitedOn: chance.timestamp(),
respondedOn: null,
name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
},
save: jest.fn(() => fragments.fragment),
owners: [user.id],
},
}
module.exports = collections
module.exports = fragments
......@@ -2,9 +2,11 @@ const Chance = require('chance')
const chance = new Chance()
const heID = chance.guid()
const revId = chance.guid()
const authorID = chance.guid()
module.exports = {
heTeamID: heID,
revTeamID: revId,
authorTeamID: authorID,
}
const users = require('./users')
const collections = require('./collections')
const { heTeamID, revTeamID } = require('./teamIDs')
const fragments = require('./fragments')
const { heTeamID, revTeamID, authorTeamID } = require('./teamIDs')
const { submittingAuthor } = require('./userData')
const { collection } = collections
const { fragment } = fragments
const { handlingEditor, reviewer } = users
const teams = {
heTeam: {
......@@ -29,13 +33,29 @@ const teams = {
group: 'reviewer',
name: 'reviewer',
object: {
type: 'collection',
id: collection.id,
type: 'fragment',
id: fragment.id,
},
members: [reviewer.id],
save: jest.fn(() => teams.revTeam),
updateProperties: jest.fn(() => teams.revTeam),
id: revTeamID,
},
authorTeam: {
teamType: {
name: 'author',
permissions: 'author',
},
group: 'author',
name: 'author',
object: {
type: 'fragment',
id: fragment.id,
},
members: [submittingAuthor.id],
save: jest.fn(() => teams.authorTeam),
updateProperties: jest.fn(() => teams.authorTeam),
id: authorTeamID,
},
}
module.exports = teams
......@@ -15,5 +15,7 @@ module.exports = {
author: generateUserData(),
reviewer: generateUserData(),
answerReviewer: generateUserData(),
submittingAuthor: generateUserData(),
recReviewer: generateUserData(),
answerHE: generateUserData(),
}
const { heTeamID, revTeamID } = require('./teamIDs')
const { heTeamID, revTeamID, authorTeamID } = require('./teamIDs')
const {
handlingEditor,
user,
......@@ -6,6 +6,9 @@ const {
author,
reviewer,
answerReviewer,
submittingAuthor,
recReviewer,
answerHE,
} = require('./userData')
const Chance = require('chance')
......@@ -51,6 +54,21 @@ const users = {
handlingEditor: true,
title: 'Mr',
},
answerHE: {
type: 'user',
username: chance.word(),
email: answerHE.email,
password: 'password',
admin: false,
id: answerHE.id,
firstName: answerHE.firstName,
lastName: answerHE.lastName,
teams: [heTeamID],
save: jest.fn(() => users.answerHE),
editorInChief: false,
handlingEditor: true,
title: 'Mr',
},
user: {
type: 'user',
username: chance.word(),
......@@ -65,6 +83,8 @@ const users = {
title: 'Mr',
save: jest.fn(() => users.user),
isConfirmed: false,
updateProperties: jest.fn(() => users.user),
teams: [],
},
author: {
type: 'user',
......@@ -79,6 +99,8 @@ const users = {
title: 'Mr',
save: jest.fn(() => users.author),
isConfirmed: true,
passwordResetToken: chance.hash(),
teams: [authorTeamID],
},
reviewer: {
type: 'user',
......@@ -112,6 +134,36 @@ const users = {
teams: [revTeamID],
invitationToken: 'inv-token-123',
},
submittingAuthor: {
type: 'user',
username: 'sauthor',
email: submittingAuthor.email,
password: 'password',
admin: false,
id: submittingAuthor.id,
passwordResetToken: chance.hash(),
firstName: submittingAuthor.firstName,
lastName: submittingAuthor.lastName,
affiliation: chance.company(),
title: 'Mr',
save: jest.fn(() => users.submittingAuthor),
isConfirmed: false,
},
recReviewer: {
type: 'user',
username: chance.word(),
email: recReviewer.email,
password: 'password',
admin: false,
id: recReviewer.id,
firstName: recReviewer.firstName,
lastName: recReviewer.lastName,
affiliation: chance.company(),
title: 'Mr',
save: jest.fn(() => users.recReviewer),
isConfirmed: true,
teams: [revTeamID],
},
}
module.exports = users
// const fixtures = require('../fixtures/fixtures')
const UserMock = require('../mocks/User')
const TeamMock = require('../mocks/Team')
......@@ -18,12 +16,17 @@ const build = fixtures => {
find: jest.fn(id => findMock(id, 'fragments', fixtures)),
},
}
UserMock.find = jest.fn(id => findMock(id, 'users', fixtures))
UserMock.findByEmail = jest.fn(email => findByEmailMock(email, fixtures))
UserMock.all = jest.fn(() => Object.values(fixtures.users))
UserMock.findOneByField = jest.fn((field, value) =>
findOneByFieldMock(field, value, 'users', fixtures),
)
UserMock.updateProperties = jest.fn(user =>
updatePropertiesMock(user, 'users'),
)
TeamMock.find = jest.fn(id => findMock(id, 'teams', fixtures))
TeamMock.updateProperties = jest.fn(team =>
updatePropertiesMock(team, 'teams', fixtures),
......@@ -40,7 +43,7 @@ const findMock = (id, type, fixtures) => {
fixtureObj => fixtureObj.id === id,
)
if (foundObj === undefined) return Promise.reject(notFoundError)
if (!foundObj) return Promise.reject(notFoundError)
return Promise.resolve(foundObj)
}
......
......@@ -7,12 +7,27 @@ class Collection {
this.collection = collection
}
async updateStatusByRecommendation({ recommendation }) {
let newStatus = 'pendingApproval'
if (['minor', 'major'].includes(recommendation))
newStatus = 'revisionRequested'
async updateStatusByRecommendation({
recommendation,
isHandlingEditor = false,
}) {
let newStatus
if (isHandlingEditor) {
newStatus = 'pendingApproval'
if (['minor', 'major'].includes(recommendation)) {
newStatus = 'revisionRequested'
}
} else {
if (recommendation === 'minor') {
newStatus = 'reviewCompleted'
}
await this.updateStatus({ newStatus })
if (recommendation === 'major') {
newStatus = 'reviewersInvited'
}
}
this.updateStatus({ newStatus })
}
async updateFinalStatusByRecommendation({ recommendation }) {
......@@ -36,42 +51,10 @@ class Collection {
async updateStatus({ newStatus }) {
this.collection.status = newStatus
this.collection.visibleStatus = statuses[this.collection.status].private
this.collection.visibleStatus = statuses[this.collection.status].public
await this.collection.save()
}
async getAuthorData({ UserModel }) {
const { collection: { authors } } = this
const submittingAuthorData = authors.find(
author => author.isSubmitting === true,
)
const submittingAuthor = await UserModel.find(submittingAuthorData.userId)
const authorsPromises = authors.map(async author => {
const user = await UserModel.find(author.userId)
return `${user.firstName} ${user.lastName}`
})
const authorsList = await Promise.all(authorsPromises)
return {
authorsList,
submittingAuthor,
}
}
getReviewerInvitations({ agree = true }) {
const { collection: { invitations } } = this
return agree
? invitations.filter(
inv =>
inv.role === 'reviewer' &&
inv.hasAnswer === true &&
inv.isAccepted === true,
)
: invitations.filter(
inv => inv.role === 'reviewer' && inv.hasAnswer === false,
)
}
async addHandlingEditor({ user, invitation }) {
this.collection.handlingEditor = {
id: user.id,
......
const Collection = require('./Collection')
const Fragment = require('./Fragment')
const User = require('./User')
const get = require('lodash/get')
const config = require('config')
......@@ -29,15 +29,17 @@ class Email {
recommendation = {},
isSubmitted = false,
agree = false,
FragmentModel,
}) {
const {
UserModel,
collection,
parsedFragment: { recommendations, title, type },
parsedFragment: { recommendations, title, type, id },
authors: { submittingAuthor: { firstName = '', lastName = '' } },
} = this
const collectionHelper = new Collection({ collection })
const reviewerInvitations = collectionHelper.getReviewerInvitations({
const fragment = await FragmentModel.find(id)
const fragmentHelper = new Fragment({ fragment })
const reviewerInvitations = fragmentHelper.getReviewerInvitations({
agree,
})
......@@ -98,7 +100,11 @@ class Email {
)
}
async setupAuthorsEmail({ requestToRevision = false, publish = false }) {
async setupAuthorsEmail({
requestToRevision = false,
publish = false,
FragmentModel,
}) {
const {
baseUrl,
collection,
......@@ -123,7 +129,8 @@ class Email {
},
]
} else {
toAuthors = collection.authors.map(author => ({
const fragment = await FragmentModel.find(id)
toAuthors = fragment.authors.map(author => ({
email: author.email,
name: `${author.firstName} ${author.lastName}`,
}))
......@@ -133,7 +140,7 @@ class Email {
baseUrl,
emailType,
toEmail: toAuthor.email,
handlingEditorName: collection.handlingEditor.name,
handlingEditorName: get(collection, 'handlingEditor.name'),
meta: {
collection,
authorNoteText,
......@@ -150,6 +157,7 @@ class Email {
async setupHandlingEditorEmail({
publish = false,
returnWithComments = false,
reviewSubmitted = false,
reviewerName = '',
}) {
......@@ -157,16 +165,17 @@ class Email {
baseUrl,
UserModel,
collection,
parsedFragment: { title, id },
parsedFragment: { eicComments = '', title, id },
authors: { submittingAuthor: { firstName = '', lastName = '' } },
} = this
const userHelper = new User({ UserModel })
const eic = await userHelper.getEditorInChief()
const toEmail = collection.handlingEditor.email
const toEmail = get(collection, 'handlingEditor.email')
let emailType = publish
? 'he-manuscript-published'
: 'he-manuscript-rejected'
if (reviewSubmitted) emailType = 'review-submitted'
if (returnWithComments) emailType = 'he-manuscript-return-with-comments'
mailService.sendNotificationEmail({
toEmail,
baseUrl,
......@@ -174,8 +183,9 @@ class Email {
meta: {
collection,
reviewerName,
eicComments,
eicName: `${eic.firstName} ${eic.lastName}`,
handlingEditorName: collection.handlingEditor.name,
handlingEditorName: get(collection, 'handlingEditor.name') || '',
emailSubject: `${collection.customId}: Manuscript Decision`,
fragment: {
id,
......@@ -234,7 +244,7 @@ class Email {
collection,
fragment: { id },
eicName: `${eic.firstName} ${eic.lastName}`,
handlingEditorName: collection.handlingEditor.name,
handlingEditorName: get(collection, 'handlingEditor.name'),
},
})
}
......@@ -322,7 +332,7 @@ class Email {
}
async setupReviewerUnassignEmail({ user, authorName }) {
const { collection, fragment: { title } } = this
const { collection, parsedFragment: { title = '' } } = this
await mailService.sendNotificationEmail({
toEmail: user.email,
......
const get = require('lodash/get')
class Fragment {
constructor({ fragment }) {
this.fragment = fragment
}
async getFragmentData({ handlingEditor = {} }) {
const { fragment: { metadata, recommendations = [], id } } = this
const { fragment: { metadata = {}, recommendations = [], id } } = this
const heRecommendation = recommendations.find(
rec => rec.userId === handlingEditor.id,
)
let { title, abstract } = metadata
let { title = '', abstract = '' } = metadata
const { type } = metadata
title = title.replace(/<(.|\n)*?>/g, '')
abstract = abstract ? abstract.replace(/<(.|\n)*?>/g, '') : ''
......@@ -21,6 +24,72 @@ class Fragment {
heRecommendation,
}
}
async addAuthor({ user, isSubmitting, isCorresponding }) {
const { fragment } = this
fragment.authors = fragment.authors || []
const author = {
id: user.id,
firstName: user.firstName || '',
lastName: user.lastName || '',
email: user.email,
title: user.title || '',
affiliation: user.affiliation || '',
isSubmitting,
isCorresponding,
}
fragment.authors.push(author)
await fragment.save()
return author
}
async getAuthorData({ UserModel }) {
const { fragment: { authors = [] } } = this
const submittingAuthorData = authors.find(author => author.isSubmitting)
try {
const submittingAuthor = await UserModel.find(
get(submittingAuthorData, 'id'),
)
const authorsPromises = authors.map(async author => {
const user = await UserModel.find(author.id)
return `${user.firstName} ${user.lastName}`
})
const authorsList = await Promise.all(authorsPromises)
return {
authorsList,
submittingAuthor,
}
} catch (e) {
throw e
}
}
getReviewerInvitations({ agree = true }) {
const { fragment: { invitations = [] } } = this
return agree
? invitations.filter(
inv =>
inv.role === 'reviewer' &&
inv.hasAnswer === true &&
inv.isAccepted === true,
)
: invitations.filter(
inv => inv.role === 'reviewer' && inv.hasAnswer === false,
)
}
getHeRequestToRevision() {
const { fragment: { recommendations = [] } } = this
return recommendations.find(
rec =>
rec.recommendationType === 'editorRecommendation' &&
(rec.recommendation === 'minor' || rec.recommendation === 'major'),
)
}
}
module.exports = Fragment
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment