Skip to content
Snippets Groups Projects
Commit ddb6d53e authored by Jure's avatar Jure
Browse files

refactor: change the ReviewPage to use hooks instead of recompose

parent c77b43c5
No related branches found
No related tags found
No related merge requests found
Showing
with 368 additions and 1120 deletions
import React from 'react'
import { Link } from '@pubsweet/ui'
const projectUrl = ({ journal, version, page, id }) => {
const projectUrl = ({ version, page, id }) => {
const parts = []
parts.push('journals')
parts.push(typeof journal === 'object' ? journal.id : journal)
if (version) {
parts.push('versions')
parts.push(typeof version === 'object' ? version.id : version)
......
......@@ -55,12 +55,7 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => {
{(status === 'accepted' || status === 'completed') && (
<Links>
<LinkContainer>
<JournalLink
id={version.id}
journal={journals}
page="reviews"
version={version}
>
<JournalLink id={version.id} page="reviews" version={version}>
{status === 'completed' ? 'Completed' : 'Do Review'}
</JournalLink>
</LinkContainer>
......
This diff is collapsed.
## pubsweet-component-xpub-review
A PubSweet component that provides interfaces for editors to invite reviewers, reviewers to submit their reviews, and editors to make their decisions.
_Note:
Some of the container components for these pages are a bit complex, as they need to also connect child components that are nested deeper in the tree and pass them down as props - there may be a better solution to this._
import { compose, withProps } from 'recompose'
import { graphql } from '@apollo/react-hoc'
import React from 'react'
// import { compose, withProps } from 'recompose'
import { useMutation, useQuery } from '@apollo/react-hooks'
import gql from 'graphql-tag'
import { withFormik } from 'formik'
import { withLoader } from 'pubsweet-client'
import { Formik } from 'formik'
// import { withLoader } from 'pubsweet-client'
import { cloneDeep } from 'lodash'
import { getCommentContent } from './review/util'
import ReviewLayout from '../components/review/ReviewLayout'
import { Spinner } from '../../../shared'
import useCurrentUser from '../../../../hooks/useCurrentUser'
const reviewFields = `
id
......@@ -109,6 +112,7 @@ const fragmentFields = `
}
keywords
}
submission
suggestions {
reviewers {
opposed
......@@ -134,11 +138,16 @@ const query = gql`
manuscriptVersions {
${fragmentFields}
}
channels {
id
type
topic
}
}
}
`
const updateTeam = gql`
const updateTeamMutation = gql`
mutation($id: ID!, $input: TeamInput) {
updateTeam(id: $id, input: $input) {
${teamFields}
......@@ -146,7 +155,7 @@ const updateTeam = gql`
}
`
const updateReviewMutation = gql`
const updateReviewMutationQuery = gql`
mutation($id: ID, $input: ReviewInput) {
updateReview(id: $id, input: $input) {
${reviewFields}
......@@ -177,172 +186,354 @@ const createFileMutation = gql`
}
`
export default compose(
graphql(query, {
options: ({ match }) => ({
export default ({ match, ...props }) => {
const currentUser = useCurrentUser()
const [updateReviewMutation] = useMutation(updateReviewMutationQuery)
// File upload
const [uploadReviewFiles] = useMutation(uploadReviewFilesMutation)
const [updateTeam] = useMutation(updateTeamMutation)
const [createFileM] = useMutation(createFileMutation)
const createFile = file =>
createFileM({
variables: {
id: match.params.version,
file,
},
}),
}),
graphql(uploadReviewFilesMutation, { name: 'uploadReviewFilesMutation' }),
graphql(updateReviewMutation, { name: 'updateReviewMutation' }),
graphql(updateTeam, { name: 'updateTeam' }),
graphql(createFileMutation, {
props: ({ mutate, ownProps: { match } }) => ({
createFile: file => {
mutate({
update: (proxy, { data: { createFile } }) => {
const data = proxy.readQuery({
query,
variables: {
file,
id: match.params.version,
},
update: (proxy, { data: { createFile } }) => {
const data = proxy.readQuery({
query,
variables: {
id: match.params.version,
},
})
})
data.manuscript.reviews.map(review => {
if (review.id === file.objectId) {
review.comments.map(comment => {
if (comment.type === createFile.fileType) {
comment.files = [createFile]
}
return comment
})
data.manuscript.reviews.map(review => {
if (review.id === file.objectId) {
review.comments.map(comment => {
if (comment.type === createFile.fileType) {
comment.files = [createFile]
}
return review
return comment
})
proxy.writeQuery({ query, data })
},
}
return review
})
proxy.writeQuery({ query, data })
},
}),
}),
withLoader(),
withProps(
({
manuscript,
currentUser,
match: {
params: { journal },
},
updateReviewMutation,
uploadReviewFilesMutation,
updateTeam,
createFile,
}) => ({
journal: { id: journal },
review:
manuscript.reviews.find(
review => review.user.id === currentUser.id && !review.isDecision,
) || {},
status: (
(
(manuscript.teams.find(team => team.role === 'reviewerEditor') || {})
.status || []
).find(status => status.user === currentUser.id) || {}
).status,
updateReview: (review, file) => {
;(review.comments || []).map(comment => {
delete comment.files
delete comment.__typename
return comment
})
})
const reviewData = {
recommendation: review.recommendation,
comments: review.comments,
manuscriptId: manuscript.id,
}
const { loading, error, data } = useQuery(query, {
variables: {
id: match.params.version,
},
})
return updateReviewMutation({
variables: {
id: review.id || undefined,
input: reviewData,
},
update: (proxy, { data: { updateReview } }) => {
const data = JSON.parse(
JSON.stringify(
proxy.readQuery({
query,
variables: {
id: manuscript.id,
},
}),
),
)
let reviewIndex = data.manuscript.reviews.findIndex(
review => review.id === updateReview.id,
)
reviewIndex = reviewIndex < 0 ? 0 : reviewIndex
data.manuscript.reviews[reviewIndex] = updateReview
proxy.writeQuery({ query, data })
},
})
if (loading) return <Spinner />
if (error) return `Error! ${error.message}`
const manuscript = data.manuscript
const channelId = manuscript.channels.find(c => c.type === 'editorial').id
const review =
(manuscript.reviews &&
manuscript.reviews.find(
review => review.user.id === currentUser.id && !review.isDecision,
)) ||
{}
const status = (
(
(manuscript.teams.find(team => team.role === 'reviewerEditor') || {})
.status || []
).find(status => status.user === currentUser.id) || {}
).status
const updateReview = (review, file) => {
;(review.comments || []).map(comment => {
delete comment.files
delete comment.__typename
return comment
})
const reviewData = {
recommendation: review.recommendation,
comments: review.comments,
manuscriptId: manuscript.id,
}
return updateReviewMutation({
variables: {
id: review.id || undefined,
input: reviewData,
},
uploadFile: (file, updateReview, type) =>
uploadReviewFilesMutation({
variables: {
file,
},
}).then(({ data }) => {
const newFile = {
url: data.upload.url,
filename: file.name,
mimeType: file.type,
size: file.size,
object: 'Review',
objectId: updateReview.id,
fileType: type,
}
createFile(newFile)
}),
completeReview: history => {
const team = cloneDeep(manuscript.teams).find(
team => team.role === 'reviewerEditor',
update: (proxy, { data: { updateReview } }) => {
const data = JSON.parse(
JSON.stringify(
proxy.readQuery({
query,
variables: {
id: manuscript.id,
},
}),
),
)
team.members = team.members.map(m => {
if (m.user.id === currentUser.id) {
return { user: { id: m.user.id }, status: 'completed' }
}
return { user: { id: m.user.id }, status: m.status }
})
let reviewIndex = data.manuscript.reviews.findIndex(
review => review.id === updateReview.id,
)
reviewIndex = reviewIndex < 0 ? 0 : reviewIndex
data.manuscript.reviews[reviewIndex] = updateReview
proxy.writeQuery({ query, data })
},
})
}
updateTeam({
variables: {
id: team.id,
input: {
members: team.members,
},
},
}).then(() => {
history.push('/dashboard')
})
const uploadFile = (file, updateReview, type) =>
uploadReviewFilesMutation({
variables: {
file,
},
}),
),
withFormik({
mapPropsToValues: props =>
props.manuscript.reviews.find(
review => review.user.id === props.currentUser.id && !review.isDecision,
) || {
id: null,
comments: [],
recommendation: null,
}).then(({ data }) => {
const newFile = {
url: data.upload.url,
filename: file.name,
mimeType: file.type,
size: file.size,
object: 'Review',
objectId: updateReview.id,
fileType: type,
}
createFile(newFile)
})
const completeReview = history => {
const team = cloneDeep(manuscript.teams).find(
team => team.role === 'reviewerEditor',
)
team.members = team.members.map(m => {
if (m.user.id === currentUser.id) {
return { user: { id: m.user.id }, status: 'completed' }
}
return { user: { id: m.user.id }, status: m.status }
})
updateTeam({
variables: {
id: team.id,
input: {
members: team.members,
},
},
isInitialValid: ({ review }) => {
if (!review.id) return false
const hasRecommendation = review.recommendation !== null
const comment = getCommentContent(review, 'note')
const isCommented = comment !== null && comment !== ''
}).then(() => {
history.push('/dashboard')
})
}
return isCommented && hasRecommendation
},
displayName: 'review',
handleSubmit: (props, { props: { completeReview, history } }) =>
completeReview(history),
}),
)(ReviewLayout)
return (
<Formik
handleSubmit={(props, { props: { completeReview, history } }) =>
completeReview(history)
}
// isInitialValid: ({ review }) => {
// if (!review.id) return false
// const hasRecommendation = review.recommendation !== null
// const comment = getCommentContent(review, 'note')
// const isCommented = comment !== null && comment !== ''
// return isCommented && hasRecommendation
// },
initialValues={
(manuscript.reviews &&
manuscript.reviews.find(
review => review.user.id === currentUser.id && !review.isDecision,
)) || {
id: null,
comments: [],
recommendation: null,
}
}
>
<ReviewLayout
currentUser={currentUser}
manuscript={manuscript}
review={review}
status={status}
updateReview={updateReview}
uploadFile={uploadFile}
channelId={channelId}
/>
</Formik>
)
}
// export default compose(
// graphql(query, {
// options: ({ match }) => ({
// variables: {
// id: match.params.version,
// },
// }),
// }),
// graphql(uploadReviewFilesMutation, { name: 'uploadReviewFilesMutation' }),
// graphql(updateReviewMutation, { name: 'updateReviewMutation' }),
// graphql(updateTeam, { name: 'updateTeam' }),
// graphql(createFileMutation, {
// props: ({ mutate, ownProps: { match } }) => ({
// createFile: file => {
// mutate({
// variables: {
// file,
// },
// update: (proxy, { data: { createFile } }) => {
// const data = proxy.readQuery({
// query,
// variables: {
// id: match.params.version,
// },
// })
// data.manuscript.reviews.map(review => {
// if (review.id === file.objectId) {
// review.comments.map(comment => {
// if (comment.type === createFile.fileType) {
// comment.files = [createFile]
// }
// return comment
// })
// }
// return review
// })
// proxy.writeQuery({ query, data })
// },
// })
// },
// }),
// }),
// withLoader(),
// withProps(
// ({
// manuscript,
// currentUser,
// match: {
// params: { journal },
// },
// updateReviewMutation,
// uploadReviewFilesMutation,
// updateTeam,
// createFile,
// }) => ({
// journal: { id: journal },
// review:
// manuscript.reviews.find(
// review => review.user.id === currentUser.id && !review.isDecision,
// ) || {},
// status: (
// (
// (manuscript.teams.find(team => team.role === 'reviewerEditor') || {})
// .status || []
// ).find(status => status.user === currentUser.id) || {}
// ).status,
// updateReview: (review, file) => {
// ;(review.comments || []).map(comment => {
// delete comment.files
// delete comment.__typename
// return comment
// })
// const reviewData = {
// recommendation: review.recommendation,
// comments: review.comments,
// manuscriptId: manuscript.id,
// }
// return updateReviewMutation({
// variables: {
// id: review.id || undefined,
// input: reviewData,
// },
// update: (proxy, { data: { updateReview } }) => {
// const data = JSON.parse(
// JSON.stringify(
// proxy.readQuery({
// query,
// variables: {
// id: manuscript.id,
// },
// }),
// ),
// )
// let reviewIndex = data.manuscript.reviews.findIndex(
// review => review.id === updateReview.id,
// )
// reviewIndex = reviewIndex < 0 ? 0 : reviewIndex
// data.manuscript.reviews[reviewIndex] = updateReview
// proxy.writeQuery({ query, data })
// },
// })
// },
// uploadFile: (file, updateReview, type) =>
// uploadReviewFilesMutation({
// variables: {
// file,
// },
// }).then(({ data }) => {
// const newFile = {
// url: data.upload.url,
// filename: file.name,
// mimeType: file.type,
// size: file.size,
// object: 'Review',
// objectId: updateReview.id,
// fileType: type,
// }
// createFile(newFile)
// }),
// completeReview: history => {
// const team = cloneDeep(manuscript.teams).find(
// team => team.role === 'reviewerEditor',
// )
// team.members = team.members.map(m => {
// if (m.user.id === currentUser.id) {
// return { user: { id: m.user.id }, status: 'completed' }
// }
// return { user: { id: m.user.id }, status: m.status }
// })
// updateTeam({
// variables: {
// id: team.id,
// input: {
// members: team.members,
// },
// },
// }).then(() => {
// history.push('/dashboard')
// })
// },
// }),
// ),
// withFormik({
// mapPropsToValues: props =>
// props.manuscript.reviews.find(
// review => review.user.id === props.currentUser.id && !review.isDecision,
// ) || {
// id: null,
// comments: [],
// recommendation: null,
// },
// isInitialValid: ({ review }) => {
// if (!review.id) return false
// const hasRecommendation = review.recommendation !== null
// const comment = getCommentContent(review, 'note')
// const isCommented = comment !== null && comment !== ''
// return isCommented && hasRecommendation
// },
// displayName: 'review',
// handleSubmit: (props, { props: { completeReview, history } }) =>
// completeReview(history),
// }),
// )(ReviewLayout)
A page for an editor to make a decision on a version of a project.
```js
const { withFormik } = require('formik')
const { JournalProvider } = require('xpub-journal')
const AssignEditor = require('../assignEditors/AssignEditor').default
const journal = {
id: faker.random.uuid(),
}
const manuscriptTemplate = () => ({
id: faker.random.uuid(),
teams: [
{
id: faker.random.uuid(),
role: 'author',
name: 'Authors',
object: {
id: faker.random.uuid(),
__typename: 'Manuscript',
},
objectType: 'manuscript',
members: [
{
user: {},
},
],
},
],
meta: {
title: faker.lorem.sentence(25),
abstract: faker.lorem.sentence(100),
articleType: 'original-research',
declarations: {
openData: 'yes',
openPeerReview: 'no',
preregistered: 'yes',
previouslySubmitted: 'yes',
researchNexus: 'no',
streamlinedReview: 'no',
},
},
decision: {
id: faker.random.uuid(),
comments: [{ type: 'note', content: 'this needs review' }],
created: 'Thu Oct 11 2018',
open: false,
status: '<p>This is a decision</p>',
user: { identities: [] },
},
reviews: [
{
comments: { content: 'this needs review' },
created: 'Thu Oct 11 2018',
open: false,
recommendation: '',
user: { identities: [] },
},
],
})
const manuscript = Object.assign({}, manuscriptTemplate(), {
manuscriptVersions: [manuscriptTemplate()],
})
const team = {
members: [],
}
const options = [
{
value: faker.random.uuid(),
label: faker.internet.userName(),
},
{
value: faker.random.uuid(),
label: faker.internet.userName(),
},
{
value: faker.random.uuid(),
label: faker.internet.userName(),
},
]
const AssignEditorContainer = ({
project,
teamName,
teamTypeName,
addUserToTeam,
}) => (
<AssignEditor
team={team}
options={options}
manuscript={manuscript}
teamName={teamName}
teamTypeName={teamTypeName}
addUserToTeam={addUserToTeam}
/>
)
const ConnectedDecisionLayout = withFormik({
initialValues: {},
mapPropsToValues: ({ manuscript }) => manuscript,
displayName: 'decision',
handleSubmit: (props, { props: { onSubmit, history } }) =>
onSubmit(props, { history }),
})(DecisionLayout)
;<div style={{ position: 'relative', height: 600 }}>
<JournalProvider journal={journal}>
<ConnectedDecisionLayout manuscript={manuscript} uploadFile={() => {}} />
</JournalProvider>
</div>
```
A review of a version of a project.
```js
const review = {
comments: [{ content: 'this needs review' }],
created: 'Thu Oct 11 2018',
open: false,
recommendation: 'revise',
user: { id: 1 },
}
;<Review review={review} />
```
A form for entering a review of a version of a project.
```js
const { withFormik } = require('formik')
const journal = {
id: faker.random.uuid(),
}
const manuscriptTemplate = () => ({
id: faker.random.uuid(),
teams: [
{
id: faker.random.uuid(),
role: 'reviewerEditor',
name: 'reviewer',
object: {
id: faker.random.uuid(),
__typename: 'Manuscript',
},
objectType: 'manuscript',
members: [
{
user: { id: 1 },
},
],
},
],
meta: {
title: faker.lorem.sentence(25),
abstract: faker.lorem.sentence(100),
articleType: 'original-research',
declarations: {
openData: 'yes',
openPeerReview: 'no',
preregistered: 'yes',
previouslySubmitted: 'yes',
researchNexus: 'no',
streamlinedReview: 'no',
},
},
decision: {
id: faker.random.uuid(),
comments: [{ type: 'note', content: 'this needs review' }],
created: 'Thu Oct 11 2018',
open: false,
status: '<p>This is a decision</p>',
user: { id: 1 },
},
reviews: [
{
comments: [{ content: 'this needs review' }],
created: 'Thu Oct 11 2018',
open: false,
recommendation: '',
user: { id: 1 },
},
],
})
const manuscript = Object.assign({}, manuscriptTemplate(), {
manuscriptVersions: [manuscriptTemplate()],
})
const review = {
comments: [{ content: 'this needs review' }],
created: 'Thu Oct 11 2018',
open: false,
recommendation: '',
user: { id: 1 },
}
const currentUser = {
id: 1,
}
const ConnectedReviewForm = withFormik({
initialValues: {},
mapPropsToValues: ({ manuscript, currentUser }) =>
manuscript.reviews.find(review => review.user.id === currentUser.id),
displayName: 'review',
handleSubmit: (props, { props: { onSubmit, history } }) =>
onSubmit(props, { history }),
})(ReviewForm)
;<JournalProvider journal={journal}>
<ConnectedReviewForm
manuscript={manuscript}
currentUser={currentUser}
uploadFile={() => new XMLHttpRequest()}
/>
</JournalProvider>
```
......@@ -7,7 +7,8 @@ import ReviewForm from './ReviewForm'
import ReviewMetadata from '../metadata/ReviewMetadata'
import Review from './Review'
import EditorSection from '../decision/EditorSection'
import { Columns, Manuscript, AdminSection } from '../style'
import { Columns, Manuscript, Chat } from '../style'
import MessageContainer from '../../../../component-chat/src'
const addEditor = (manuscript, label) => ({
content: <EditorSection manuscript={manuscript} />,
......@@ -25,6 +26,7 @@ const ReviewLayout = ({
status,
updateReview,
uploadFile,
channelId,
}) => {
const reviewSections = []
const editorSections = []
......@@ -37,10 +39,14 @@ const ReviewLayout = ({
<div>
<ReviewMetadata manuscript={manuscript} />
<Review
review={manuscript.reviews.find(
review =>
(review.user.id === currentUser.id && !review.isDecision) || {},
)}
review={
manuscript.reviews &&
manuscript.reviews.find(
review =>
(review.user.id === currentUser.id && !review.isDecision) ||
{},
)
}
/>
</div>
),
......@@ -79,20 +85,23 @@ const ReviewLayout = ({
return (
<Columns>
<Manuscript>
{/* <Manuscript>
<Tabs
activeKey={editorSections[editorSections.length - 1].key}
sections={editorSections}
title="Versions"
/>
</Manuscript>
</Manuscript> */}
<AdminSection>
<Tabs
activeKey={reviewSections[reviewSections.length - 1].key}
sections={reviewSections}
title="Versions"
/>
</AdminSection>
</Manuscript>
<Chat>
<MessageContainer channelId={channelId} />
</Chat>
</Columns>
)
}
......
A page for a reviewer to submit a review of a version of a project.
```js
const { withFormik } = require('formik')
const journal = {
id: faker.random.uuid(),
}
const manuscriptTemplate = () => ({
id: faker.random.uuid(),
teams: [
{
id: faker.random.uuid(),
role: 'reviewerEditor',
name: 'reviewer',
object: {
id: faker.random.uuid(),
__typename: 'Manuscript',
},
objectType: 'manuscript',
members: [
{
user: { id: 1 },
},
],
},
],
meta: {
title: faker.lorem.sentence(25),
abstract: faker.lorem.sentence(100),
articleType: 'original-research',
declarations: {
openData: 'yes',
openPeerReview: 'no',
preregistered: 'yes',
previouslySubmitted: 'yes',
researchNexus: 'no',
streamlinedReview: 'no',
},
},
decision: {
id: faker.random.uuid(),
comments: [{ type: 'note', content: 'this needs review' }],
created: 'Thu Oct 11 2018',
open: false,
status: '<p>This is a decision</p>',
user: { id: 1 },
},
reviews: [
{
comments: [{ content: 'this needs review' }],
created: 'Thu Oct 11 2018',
open: false,
recommendation: '',
user: { id: 1 },
},
],
})
const manuscript = Object.assign({}, manuscriptTemplate(), {
manuscriptVersions: [manuscriptTemplate()],
})
const review = {
comments: [{ content: 'this needs review' }],
created: 'Thu Oct 11 2018',
open: false,
recommendation: '',
user: { id: 1 },
}
const currentUser = {
id: 1,
}
const ConnectedReviewLayout = withFormik({
initialValues: {},
mapPropsToValues: ({ manuscript, currentUser }) =>
manuscript.reviews.find(review => review.user.id === currentUser.id),
displayName: 'review',
handleSubmit: (props, { props: { onSubmit, history } }) =>
onSubmit(props, { history }),
})(ReviewLayout)
;<div style={{ position: 'relative', height: 600 }}>
<ConnectedReviewLayout
journal={journal}
manuscript={manuscript}
review={review}
uploadFile={() => {}}
currentUser={currentUser}
/>
</div>
```
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