Commit 7c517fe6 authored by Ana Ellis's avatar Ana Ellis
Browse files

Merge branch 'accept-reject-review-e2e-test' into submit-review-e2e-test

parents e5920ce7 a09b67a8
......@@ -103,19 +103,19 @@ push:latest:
only:
- master
staging:xpub-collabra:
image: pubsweet/deployer:latest
stage: staging
variables:
PACKAGE_NAME: xpub-collabra
environment:
name: $PACKAGE_NAME/staging
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
only:
- master
script:
- source deploy.sh
- create_deployment
# staging:xpub-collabra:
# image: pubsweet/deployer:latest
# stage: staging
# variables:
# PACKAGE_NAME: xpub-collabra
# environment:
# name: $PACKAGE_NAME/staging
# url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
# only:
# - master
# script:
# - source deploy.sh
# - create_deployment
# review:xpub-collabra:
# image: pubsweet/deployer:latest
......
......@@ -9,8 +9,8 @@ This is the current set of features and their status on our roadmap.
|Module |Description |In progress |Done |Issue
|:--- |--- |:---: |:---: |:---:
|**Current**|
|Summary Info |Update text for metadata questions |✔ | |#165
|Summary Info |Add instructions for uploading files |✔ | |#168
|Summary Info |Update text for metadata questions | |✔ |#165
|Summary Info |Add instructions for uploading files | |✔ |#168
|System |Apply Coko theme |✔ | |pubsweet/pubsweet#372
|System |Implement roles & permissions |✔ | |#58
|System |Write integration tests |✔ | |
......
......@@ -13,6 +13,8 @@ export default {
value: 'no',
},
],
description:
'The journal requires data be openly available, and our full policy is <a href="https://www.collabra.org/about/editorialpolicies/#open-data-open-analytic-methods-code-and-research-materials-transparency" target="_blank">here</a>. If you have exceptions that need to be considered, please click "No" and explain in your cover letter below. Please click N/A if your submission does not feature data.',
},
{
id: 'previouslySubmitted',
......@@ -27,6 +29,8 @@ export default {
value: 'no',
},
],
description:
'Provide further details in your cover letter below, if necessary.',
},
{
id: 'openPeerReview',
......@@ -41,6 +45,8 @@ export default {
value: 'no',
},
],
description:
'Please read a description of our <a href="https://www.collabra.org/about/editorialpolicies/#open-peer-review" target="_blank">“Open Review”</a> option and select “Yes” if you choose this process.',
},
{
id: 'streamlinedReview',
......@@ -55,10 +61,13 @@ export default {
value: 'no',
},
],
description:
'Please read a description of our <a href="https://www.collabra.org/about/editorialpolicies/#streamlined-review" target="_blank">“Streamlined Review”</a> option and select “Yes” if you choose this process. If “Yes”, please upload your ported decision letter and reviews as “Supplementary Files” below, clearly labeled.',
},
{
id: 'researchNexus',
legend: 'Submitted as part of the research nexus ?',
legend:
'Submitted as part of the <a href="https://www.collabra.org/collections/special/" target="_blank">research nexus</a> ?',
options: [
{
label: 'Yes',
......@@ -69,6 +78,8 @@ export default {
value: 'no',
},
],
description:
'If yes, mention the name of the Research Nexus in your cover letter below.',
},
{
id: 'preregistered',
......@@ -83,6 +94,8 @@ export default {
value: 'no',
},
],
description:
'If any or all elements of your study have been pre-registered, click yes and ensure details are in the Acknowledgements section of your manuscript, following these <a href="https://www.collabra.org/about/editorialpolicies/#preregistration-of-studies-and-analysis-plans" target="_blank">guidelines</a>.',
},
],
}
......@@ -8,3 +8,5 @@ export { default as articleTypes } from './article-types'
export { default as editors } from './editors'
export { default as roles } from './roles'
export { default as reviewStatus } from './review-status'
export { default as notes } from './submit-notes'
export { default as supplementary } from './supplementary'
export default {
fundingAcknowledgement: {
placeholder: 'Enter an acknowledgment…',
title: 'Funding body acknowledgement (required)',
description:
'Please also specifically state if your work was <u>not</u> supported by funding.',
},
specialInstructions: {
placeholder: 'Enter instructions for the editor…',
title: 'Special instructions (confidential, to Editors only)',
description:
'Please write or paste what you would normally write into a cover letter here, focusing on information that has not been covered by this form, or any further information prompted by your answers to the questions above.',
},
}
export default {
description:
'<pre>There are 3 types of item which should be uploaded here:<br />' +
'<ul>' +
'<li>Regular figures which should also be be embedded in the manuscript.(These are helpful for the production<br />process,if your article is accepted, in case there are any issues with the embedded versions.)</li>' +
'<li>Supplemental items that provide a relevant and useful expansion of the article (Examples include appendices,<br/>very large tables, audios, videos, three-dimensional visualizations, interactive graphics, and so on.)</li>' +
'<li>Items that support the peer review process. (Examples are ported reviews and decision letters for use during<br />the Streamlined Review workflow. Also, if you have a long cover letter you would rather upload than paste<br/>into the text box, please also upload it here.)</li>' +
'</ul>' +
'Supplemental materials can be named in almost any way, provided that the files are clearly and consistently named,<br />are uploaded in chronological order, and grouped as they are described above. For example, a typical article<br/>might include:' +
'<br />' +
'<ul>' +
'<li>Figure 1.jpg</li>' +
'<li>Figure 2.jpg</li>' +
'<li>Figure 3.jpg</li>' +
'<li>Supplemental Table 1.docx</li>' +
'<li>Ported Decision Letter and Reviews from Journal X.docx</li>' +
'</ul>' +
'</pre>',
}
......@@ -13,7 +13,7 @@ class XpubCollabraMode {
constructor(userId, operation, object, context) {
this.userId = userId
this.operation = XpubCollabraMode.mapOperation(operation)
this.object = object
this.object = object.current ? object.current : object
this.context = context
}
......@@ -62,7 +62,7 @@ class XpubCollabraMode {
const memberships = await Promise.all(
this.user.teams.map(async teamId => {
const team = await this.context.models.Team.find(teamId)
if (!team) return [false]
return membershipCondition(team)
}),
)
......@@ -254,12 +254,17 @@ class XpubCollabraMode {
if (!this.isAuthenticated()) {
return false
}
const fragment = this.object
let permission = this.isAuthor(fragment)
permission = permission ? true : this.isAssignedReviewerEditor(fragment)
permission = permission ? true : this.isAssignedManagingEditor(fragment)
// Caveat: this means every logged-in user can read every fragment (but needs its UUID)
// Ideally we'd check if the fragment (version) belongs to a collection (project)
// where the user is a member of a team with the appropriate rights. However there is no
// link from a fragment back to a collection at this point. Something to keep in mind!
return true
return permission
}
/**
......@@ -444,6 +449,7 @@ class XpubCollabraMode {
(await this.isAssignedHandlingEditor(this.object)) ||
(await this.isAssignedSeniorEditor(this.object)) ||
(await this.isAssignedReviewerEditor(this.object))
return permission
}
......@@ -517,6 +523,63 @@ class XpubCollabraMode {
return collection.some(collection => collection)
}
async canViewPage() {
this.user = await this.context.models.User.find(this.userId)
const { path, params } = this.object
if (path === '/projects/:project/versions/:version/submit') {
return this.checkPageSubmit(params)
}
if (path === '/projects/:project/versions/:version/review') {
return this.checkPageReview(params)
}
if (path === '/projects/:project/versions/:version/decision/:project') {
return this.checkPageDecision(params)
}
return true
}
async checkPageSubmit(params) {
const collection = this.context.models.Collection.find(params.project)
let permission = await this.isAuthor(collection)
permission = permission
? true
: await !this.canReadatLeastOneFragmentOfCollection(collection, [
'isAssignedReviewerEditor',
])
permission = permission
? true
: await this.checkTeamMembers(
['isAssignedSeniorEditor', 'isAssignedHandlingEditor'],
collection,
)
return permission
}
async checkPageReview(params) {
const collection = this.context.models.Collection.find(params.project)
const version = this.context.models.Fragment.find(params.version)
if (this.isAuthor(collection)) return false
let permission = await this.checkTeamMembers(
['isAssignedSeniorEditor', 'isAssignedHandlingEditor'],
collection,
)
// set object to Fragment so can validate on canReadFragment
this.object = version
permission = permission ? true : await this.canReadFragment()
return permission
}
async checkTeamMembers(team, object) {
const permission = await Promise.all(team.map(t => this[t](object)))
return permission.includes(true)
......@@ -533,43 +596,43 @@ module.exports = {
// currently we take for granted that an admin is the Managing Editor
// Temporally we need this if statement to prevent admin from seeing
// review and submission section on dashboard (ME permissions)
if (
operation === 'can view review section' ||
operation === 'can view my submission section'
)
return false
// if (
// operation === 'can view review section' ||
// operation === 'can view my submission section'
// )
// return false
return user.admin
},
GET: (userId, operation, object, context) => {
const mode = new XpubCollabraMode(userId, operation, object, context)
// GET /api/collections
if (object && object.path === '/collections') {
if (mode.object && mode.object.path === '/collections') {
return mode.canListCollections()
}
// GET /api/users
if (object && object.path === '/users') {
if (mode.object && mode.object.path === '/users') {
return mode.canListUsers()
}
// GET /api/fragments
if (object && object.path === '/fragments') {
if (mode.object && mode.object.path === '/fragments') {
return mode.canListFragments()
}
// GET /api/teams
if (object && object.path === '/teams') {
if (mode.object && mode.object.path === '/teams') {
return mode.canListTeams()
}
// GET /api/collection
if (object && object.type === 'collection') {
if (mode.object && mode.object.type === 'collection') {
return mode.canReadCollection()
}
// GET /api/fragment
if (object && object.type === 'fragment') {
if (mode.object && mode.object.type === 'fragment') {
return mode.canReadFragment()
}
......@@ -579,7 +642,7 @@ module.exports = {
}
// GET /api/user
if (object && object.type === 'user') {
if (mode.object && mode.object.type === 'user') {
return mode.canReadUser()
}
......@@ -589,27 +652,30 @@ module.exports = {
const mode = new XpubCollabraMode(userId, operation, object, context)
// POST /api/collections
if (object && object.path === '/collections') {
if (mode.object && mode.object.path === '/collections') {
return mode.canCreateCollection()
}
// POST /api/users
if (object && object.path === '/users') {
if (mode.object && mode.object.path === '/users') {
return mode.canCreateUser()
}
// POST /api/fragments
if (object && object.path === '/fragments') {
if (mode.object && mode.object.path === '/fragments') {
return mode.canCreateFragment()
}
// POST /api/collections/:collectionId/fragments
if (object && object.path === '/collections/:collectionId/fragments') {
if (
mode.object &&
mode.object.path === '/collections/:collectionId/fragments'
) {
return mode.canCreateFragmentInACollection()
}
// POST /api/teams
if (object && object.path === '/teams') {
if (mode.object && mode.object.path === '/teams') {
return mode.canCreateTeam()
}
......@@ -619,27 +685,27 @@ module.exports = {
const mode = new XpubCollabraMode(userId, operation, object, context)
// PATCH /api/make-invitation
if (object && object.path === '/make-invitation') {
if (mode.object && mode.object.path === '/make-invitation') {
return mode.canMakeInvitation()
}
// PATCH /api/collections/:id
if (object && object.type === 'collection') {
if (mode.object && mode.object.type === 'collection') {
return mode.canUpdateCollection()
}
// PATCH /api/users/:id
if (object && object.type === 'user') {
if (mode.object && mode.object.type === 'user') {
return mode.canUpdateUser()
}
// PATCH /api/fragments/:id
if (object && object.type === 'fragment') {
if (mode.object && mode.object.type === 'fragment') {
return mode.canUpdateFragment()
}
// PATCH /api/teams/:id
if (object && object.type === 'team') {
if (mode.object && mode.object.type === 'team') {
return mode.canUpdateTeam()
}
......@@ -687,6 +753,10 @@ module.exports = {
const mode = new XpubCollabraMode(userId, operation, object, context)
return mode.canViewReviewSection()
},
'can view page': (userId, operation, object, context) => {
const mode = new XpubCollabraMode(userId, operation, object, context)
return mode.canViewPage()
},
create: (userId, operation, object, context) => {
const mode = new XpubCollabraMode(userId, operation, object, context)
......
......@@ -14,8 +14,9 @@
"url": "https://gitlab.coko.foundation/xpub/xpub"
},
"dependencies": {
"@pubsweet/coko-theme": "2.0.0",
"@pubsweet/ui": "4.1.3",
"@pubsweet/coko-theme": "2.1.0",
"@pubsweet/ui": "5.0.1",
"@pubsweet/db-manager": "^1.1.0",
"babel-core": "^6.26.0",
"config": "^1.26.2",
"faker": "^4.1.0",
......@@ -26,21 +27,21 @@
"loadable-components": "^0.3.0",
"moment": "^2.18.1",
"prop-types": "^15.5.10",
"pubsweet": "2.2.1",
"pubsweet-client": "3.0.0",
"pubsweet": "^2.2.4",
"pubsweet-client": "3.0.1",
"pubsweet-component-ink-backend": "^0.1.1",
"pubsweet-component-ink-frontend": "^1.0.0",
"pubsweet-component-ink-frontend": "^1.0.2",
"pubsweet-component-login": "^1.0.1",
"pubsweet-component-signup": "^1.0.0",
"pubsweet-component-teams-manager": "^1.1.8",
"pubsweet-component-users-manager": "^2.0.1",
"pubsweet-component-xpub-dashboard": "^1.0.0",
"pubsweet-component-xpub-dashboard": "^1.0.1",
"pubsweet-component-xpub-find-reviewers": "^0.0.6",
"pubsweet-component-xpub-manuscript": "^0.0.19",
"pubsweet-component-xpub-review": "^1.0.0",
"pubsweet-component-xpub-review-backend": "^0.2.1",
"pubsweet-component-xpub-submit": "^2.0.0",
"pubsweet-server": "^3.0.0",
"pubsweet-component-xpub-manuscript": "^0.0.20",
"pubsweet-component-xpub-review": "^1.0.1",
"pubsweet-component-xpub-review-backend": "^0.2.2",
"pubsweet-component-xpub-submit": "^2.0.1",
"pubsweet-server": "^7.1.1",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-router-dom": "^4.2.2",
......@@ -50,7 +51,7 @@
"redux-logger": "^3.0.1",
"supertest": "^3.0.0",
"winston": "^2.4.0",
"xpub-journal": "^0.0.5",
"xpub-journal": "^0.0.6",
"xpub-selectors": "^0.1.0",
"xpub-theme": "^0.0.7"
},
......
......@@ -2,6 +2,7 @@ import config from 'config'
import { Selector } from 'testcafe'
import { createSubmission } from './helpers/submission'
import { startServer, setup, teardown } from './helpers/setup'
import { setupWithTwoUnsubmittedManuscripts } from './fixtures/manuscript-setup/setup-two-unsubmitted'
import { login, dashboard, submission, confirmation } from './pageObjects'
const goodInkConfig = {
......@@ -21,16 +22,13 @@ fixture
config['pubsweet-component-ink-backend'] = goodInkConfig
await startServer()
})
.beforeEach(async () => {
const result = await setup()
author = result.userData
await login.doLogin(author.username, author.password)
})
.afterEach(teardown)
test.skip('Manage submissions journey, create new submission', async t => {
test.skip.before(async t => {
const result = await setup()
author = result.userData
await login.doLogin(author.username, author.password)
})('create new submission', async t => {
await t.expect(Selector(dashboard.mySubmissionsTitle).exists).notOk()
await createSubmission('./fixtures/testSubmission1.docx')
......@@ -48,12 +46,10 @@ test.skip('Manage submissions journey, create new submission', async t => {
test.skip
.before(async t => {
startServer()
const result = await setup()
author = result.userData
await login.doLogin(author.username, author.password)
})('Manage submissions journey, failed new submission', async t => {
})('Failed new submission', async t => {
await t
.setFilesToUpload(dashboard.createSubmission, ['./testSubmission1.docx'])
.expect(await Selector('div').withText('Internal Server Error').exists)
......@@ -67,25 +63,27 @@ test.skip
)
})
test.skip('Author submits manuscript', async t => {
await createSubmission('./fixtures/testSubmission1.docx')
await t.click(dashboard.collabraHome)
await createSubmission('./fixtures/testSubmission2.docx')
test.skip.before(async t => {
await setupWithTwoUnsubmittedManuscripts()
await login.doLogin('john', 'johnjohn')
})('Author submits manuscript', async t => {
await t
.wait(500)
.pressKey('enter')
.wait(1000)
.click(dashboard.submissionSummaryInfoLink(1))
.wait(1000)
.click(submission.submit.withText('SUBMIT YOUR MANUSCRIPT'))
.wait(1000)
.expect(confirmation.returnToSubmission.exists)
.ok()
await t.wait(500).click(confirmation.returnToSubmission)
await t.expect(Selector(submission.submit).exists).ok()
await t
.click(submission.fundingAcknowledgement)
.pressKey('tab tab tab tab tab tab tab tab enter')
.expect(
Selector(submission.submit.withText('SUBMIT YOUR MANUSCRIPT')).exists,
)
.ok()
await t.click(submission.submit.withText('SUBMIT YOUR MANUSCRIPT'))
await t
.wait(1000)
......@@ -95,11 +93,6 @@ test.skip('Author submits manuscript', async t => {
await t.wait(1000).click(confirmation.submitManuscript)
await t
.wait(3000)
.expect(await Selector(dashboard.createSubmission).exists)
.ok()
await t
.expect(Selector(dashboard.myManuscriptsTitle).exists) // fails when admin user == false
.ok()
......@@ -115,20 +108,26 @@ test.skip('Author submits manuscript', async t => {
.expect(Selector(dashboard.submissionStatus(1)).exists)
.ok()
.expect(dashboard.submissionStatus(1).innerText)
.contains('SUBMITTED')
.contains('UNSUBMITTED')
await t
.expect(Selector(dashboard.submissionStatus(2)).exists)
.ok()
.expect(dashboard.submissionStatus(2).innerText)
.contains('UNSUBMITTED')
.contains('SUBMITTED')
.wait(5000)
await t
.expect(Selector(dashboard.submissionSummaryInfoLink(0)).exists)
.expect(Selector(dashboard.submissionSummaryInfoLink(2)).exists)
.ok()
.click(dashboard.submissionSummaryInfoLink(0))
.click(dashboard.submissionSummaryInfoLink(2))
.wait(5000)
.expect(Selector(submission.authorFirstName).exists)
.ok()
await t
.expect(
Selector(submission.submit.withText('Submit your manuscript').exists),
Selector(submission.submit.withText('submit your manuscript')).exists,
)
.notOk()
})
......@@ -40,18 +40,18 @@ describe('server integration', () => {
expect(collection.type).toEqual(fixtures.collection.type)
})
it('can create a collection through GraphQL', async () => {
const { body } = await api.graphql.query(
`mutation($input: String) {
createCollection(input: $input) { id, title, status }
}`,
{
input: JSON.stringify(collectionPaper1),
},
adminToken,
)
expect(body.data.createCollection.title).toEqual(collectionPaper1.title)
})
// it('can create a collection through GraphQL', async () => {
// const { body } = await api.graphql.query(
// `mutation($input: String) {
// createCollection(input: $input) { id, title, status }
// }`,
// {
// input: JSON.stringify(collectionPaper1),
// },
// adminToken,
// )
// expect(body.data.createCollection.title).toEqual(collectionPaper1.title)
// })
})
describe('user', () => {
......@@ -138,46 +138,46 @@ describe('server integration', () => {
})
})