From fc58ad6a46ab6ac1b44cb0eb7aebfddfa542e3f9 Mon Sep 17 00:00:00 2001 From: Jure Triglav <juretriglav@gmail.com> Date: Thu, 13 Aug 2020 15:15:10 +0200 Subject: [PATCH] feat: improve file handling in review and decision component --- .../src/components/DecisionPage.js | 201 +++------ .../src/components/ReviewPage.js | 401 ++++-------------- .../src/components/ReviewersPage.js | 20 +- .../components/assignEditors/AssignEditor.js | 8 +- .../src/components/decision/DecisionForm.js | 132 +++--- .../components/decision/DecisionReviews.js | 2 +- .../src/components/review/Review.js | 18 +- .../src/components/review/ReviewForm.js | 211 ++++----- .../src/components/review/util.js | 46 +- .../src/components/reviewers/Reviewers.js | 4 +- 10 files changed, 359 insertions(+), 684 deletions(-) diff --git a/app/components/component-review/src/components/DecisionPage.js b/app/components/component-review/src/components/DecisionPage.js index cbb412e57f..09ba62e323 100644 --- a/app/components/component-review/src/components/DecisionPage.js +++ b/app/components/component-review/src/components/DecisionPage.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useRef, useEffect } from 'react' import moment from 'moment' import { Tabs } from '@pubsweet/ui' @@ -15,7 +15,6 @@ import { AdminSection, Columns, Manuscript, Chat } from './style' import { Spinner } from '../../../shared' -import { getCommentContent } from './review/util' import MessageContainer from '../../../component-chat/src' const addEditor = (manuscript, label) => ({ @@ -24,23 +23,34 @@ const addEditor = (manuscript, label) => ({ label, }) +const commentFields = ` + id + commentType + content + files { + id + created + label + filename + fileType + mimeType + size + url + } +` + const reviewFields = ` id created updated - comments { - type - content - files { - id - created - label - filename - fileType - mimeType - size - url - } + decisionComment { + ${commentFields} + } + reviewComment { + ${commentFields} + } + confidentialComment { + ${commentFields} } isDecision recommendation @@ -71,9 +81,8 @@ const fragmentFields = ` id name role - object { - objectId - objectType + manuscript { + id } members { id @@ -153,29 +162,6 @@ const updateReviewMutationQuery = gql` } ` -const uploadReviewFilesMutation = gql` - mutation($file: Upload!) { - upload(file: $file) { - url - } - } -` - -// const createFileMutation = gql` -// mutation($file: Upload!) { -// createFile(file: $file) { -// id -// created -// label -// filename -// fileType -// mimeType -// size -// url -// } -// } -// ` - const makeDecisionMutation = gql` mutation($id: ID!, $decision: String) { makeDecision(id: $id, decision: $decision) { @@ -184,41 +170,6 @@ const makeDecisionMutation = gql` } } ` - -// const updateCacheForFileCreation = (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 }) -// } - -// const createFile = file => { - -// mutate({ -// variables: { -// file, -// }, -// update: -// }, - -// - const dateLabel = date => moment(date).format('YYYY-MM-DD') const decisionSections = ({ @@ -335,21 +286,33 @@ const decisionSections = ({ const DecisionPage = ({ match }) => { // Hooks from the old world - const [makeDecision] = useMutation(makeDecisionMutation, { - // refetchQueries: [query], - }) + const [makeDecision] = useMutation(makeDecisionMutation) const [updateReviewMutation] = useMutation(updateReviewMutationQuery) - // File upload - const [uploadReviewFiles] = useMutation(uploadReviewFilesMutation) - const { loading, error, data } = useQuery(query, { variables: { id: match.params.version, }, - fetchPolicy: 'network-only', + // fetchPolicy: 'cache-and-network', }) + const reviewOrInitial = manuscript => + (manuscript && + manuscript.reviews && + manuscript.reviews.find(review => review.isDecision)) || { + comments: [], + isDecision: true, + recommendation: null, + } + + // Find an existing review or create a placeholder, and hold a ref to it + const existingReview = useRef(reviewOrInitial(data?.manuscript)) + + // Update the value of that ref if the manuscript object changes + useEffect(() => { + existingReview.current = reviewOrInitial(data?.manuscript) + }, [data?.manuscript?.reviews]) + if (loading) return <Spinner /> if (error) return `Error! ${error.message}` @@ -361,41 +324,21 @@ const DecisionPage = ({ match }) => { channelId = manuscript.channels.find(c => c.type === 'editorial').id } - const uploadFile = (file, updateReview, type) => - uploadReviewFiles({ - variables: { - file, - }, - }).then(({ data }) => { - // const newFile = { - // url: data.upload.url, - // filename: file.name, - // size: file.size, - // object: 'Review', - // objectId: updateReview.id, - // fileType: type, - // } - // createFile(newFile) - }) - - const updateReview = (data, file) => { + const updateReview = review => { const reviewData = { - isDecision: true, + recommendation: review.recommendation, manuscriptId: manuscript.id, + isDecision: true, + decisionComment: review.decisionComment && { + id: existingReview.current.decisionComment?.id, + commentType: 'decision', + content: review.decisionComment.content, + }, } - if (data.comment) { - reviewData.comments = [data.comment] - } - - if (data.recommendation) { - reviewData.recommendation = data.recommendation - } - - const review = manuscript.reviews.find(review => review.isDecision) || {} return updateReviewMutation({ variables: { - id: review.id || undefined, + id: existingReview.current.id || undefined, input: reviewData, }, update: (cache, { data: { updateReview } }) => { @@ -427,13 +370,6 @@ const DecisionPage = ({ match }) => { }, }) } - - const initialValues = (manuscript.reviews && - manuscript.reviews.find(review => review.isDecision)) || { - comments: [], - recommendation: null, - } - // const editorSectionsResult = editorSections({ manuscript }) return ( @@ -441,7 +377,8 @@ const DecisionPage = ({ match }) => { <Manuscript> <Formik displayName="decision" - initialValues={initialValues} + enableReinitialize + initialValues={existingReview} // isInitialValid={({ manuscript }) => { // const rv = // manuscript.reviews.find(review => review.isDecision) || {} @@ -459,17 +396,17 @@ const DecisionPage = ({ match }) => { }, }) }} - validate={(values, props) => { - const errors = {} - if (getCommentContent(values, 'note') === '') { - errors.comments = 'Required' - } - - if (values.recommendation === null) { - errors.recommendation = 'Required' - } - return errors - }} + // validate={(values, props) => { + // const errors = {} + // if (values.decisionComment?.content === '') { + // errors.decisionComment = 'Required' + // } + + // if (values.recommendation === null) { + // errors.recommendation = 'Required' + // } + // return errors + // }} > {props => ( // Temp @@ -488,7 +425,6 @@ const DecisionPage = ({ match }) => { handleSubmit: props.handleSubmit, isValid: props.isValid, updateReview, - uploadFile, })[decisionSections.length - 1].key } sections={decisionSections({ @@ -496,7 +432,6 @@ const DecisionPage = ({ match }) => { handleSubmit: props.handleSubmit, isValid: props.isValid, updateReview, - uploadFile, })} title="Versions" /> diff --git a/app/components/component-review/src/components/ReviewPage.js b/app/components/component-review/src/components/ReviewPage.js index d9c0e28f99..399c0a4ddc 100644 --- a/app/components/component-review/src/components/ReviewPage.js +++ b/app/components/component-review/src/components/ReviewPage.js @@ -1,30 +1,37 @@ -import React from 'react' +import React, { useRef, useEffect } from 'react' import { useMutation, useQuery } from '@apollo/client' import gql from 'graphql-tag' import { Formik } from 'formik' // 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 commentFields = ` +id +commentType +content +files { + id + created + label + filename + fileType + mimeType + size + url +} +` + const reviewFields = ` id created updated - comments { - type - content - files { - id - created - label - filename - fileType - mimeType - size - url - } + reviewComment { + ${commentFields} + } + confidentialComment { + ${commentFields} } isDecision recommendation @@ -34,23 +41,6 @@ const reviewFields = ` } ` -// const teamFields = ` -// id -// name -// role -// object { -// objectId -// objectType -// } -// members { -// id -// user { -// id -// username -// } -// } -// ` - const fragmentFields = ` id created @@ -72,9 +62,8 @@ const fragmentFields = ` id name role - object { - objectId - objectType + manuscript { + id } members { id @@ -163,87 +152,39 @@ const updateReviewMutationQuery = gql` } ` -const uploadReviewFilesMutation = gql` - mutation($file: Upload!) { - upload(file: $file) { - url - } - } -` - -const createFileMutation = gql` - mutation($file: Upload!) { - createFile(file: $file) { - id - created - label - filename - fileType - mimeType - size - url - } - } -` - export default ({ match, ...props }) => { const currentUser = useCurrentUser() const [updateReviewMutation] = useMutation(updateReviewMutationQuery) const [completeReview] = useMutation(completeReviewMutation) - // File upload - // const [uploadReviewFiles] = useMutation(uploadReviewFilesMutation) - - const [createFileM] = useMutation(createFileMutation) - const createFile = file => - createFileM({ - 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 }) - }, - }) - const { loading, error, data } = useQuery(query, { variables: { id: match.params.version, }, - fetchPolicy: 'network-only', }) + const reviewOrInitial = manuscript => + (manuscript && + manuscript.reviews && + manuscript.reviews.find( + review => review?.user?.id === currentUser.id && !review.isDecision, + )) || + {} + + // Find an existing review or create a placeholder, and hold a ref to it + const existingReview = useRef(reviewOrInitial(data?.manuscript)) + + // Update the value of that ref if the manuscript object changes + useEffect(() => { + existingReview.current = reviewOrInitial(data?.manuscript) + }, [data?.manuscript?.reviews]) + if (loading) return <Spinner /> if (error) return `Error! ${error.message}` const { manuscript } = data 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, - )) || - {} - // eslint-disable-next-line const status = ( ( @@ -253,61 +194,55 @@ export default ({ match, ...props }) => { ).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, + reviewComment: review.reviewComment && { + id: existingReview.current.reviewComment?.id, + commentType: 'review', + content: review.reviewComment.content, + }, + confidentialComment: review.confidentialComment && { + id: existingReview.current.confidentialComment?.id, + commentType: 'confidential', + content: review.confidentialComment.content, + }, } return updateReviewMutation({ variables: { - id: review.id || undefined, + id: existingReview.current.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 }) - // }, - }) - } + update: (cache, { data: { updateReview } }) => { + cache.modify({ + id: cache.identify(manuscript), + fields: { + reviews(existingReviewRefs = [], { readField }) { + const newReviewRef = cache.writeFragment({ + data: updateReview, + fragment: gql` + fragment NewReview on Review { + id + } + `, + }) + + if ( + existingReviewRefs.some( + ref => readField('id', ref) === updateReview.id, + ) + ) { + return existingReviewRefs + } - const uploadFile = (file, updateReview, type) => - uploadReviewFilesMutation({ - variables: { - file, + return [...existingReviewRefs, newReviewRef] + }, + }, + }) }, - }).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 handleSubmit = async ({ reviewId, history }) => { await completeReview({ @@ -324,7 +259,7 @@ export default ({ match, ...props }) => { initialValues={ (manuscript.reviews && manuscript.reviews.find( - review => review.user.id === currentUser.id && !review.isDecision, + review => review?.user?.id === currentUser.id && !review.isDecision, )) || { id: null, comments: [], @@ -332,12 +267,15 @@ export default ({ match, ...props }) => { } } onSubmit={values => - handleSubmit({ reviewId: review.id, history: props.history }) + handleSubmit({ + reviewId: existingReview.current.id, + history: props.history, + }) } validateOnMount={review => { if (!review.id) return false const hasRecommendation = review.recommendation !== null - const comment = getCommentContent(review, 'note') + const comment = review.decisionComment?.content const isCommented = comment !== null && comment !== '' return isCommented && hasRecommendation @@ -348,183 +286,12 @@ export default ({ match, ...props }) => { channelId={channelId} currentUser={currentUser} manuscript={manuscript} - review={review} + review={existingReview} status={status} updateReview={updateReview} - uploadFile={uploadFile} {...formikProps} /> )} </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 === 'reviewer') || {}) -// .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 === 'reviewer', -// ) -// 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) diff --git a/app/components/component-review/src/components/ReviewersPage.js b/app/components/component-review/src/components/ReviewersPage.js index 33ae162f81..3c60c1b671 100644 --- a/app/components/component-review/src/components/ReviewersPage.js +++ b/app/components/component-review/src/components/ReviewersPage.js @@ -9,9 +9,8 @@ const teamFields = ` id role name - object { - objectId - objectType + manuscript { + id } members { id @@ -46,17 +45,6 @@ const fragmentFields = ` open recommendation created - comments { - type - content - files { - fileType - id - label - url - filename - } - } user { id username @@ -114,8 +102,8 @@ const ReviewersPage = ({ match, history }) => { update: (cache, { data: { addReviewer } }) => { cache.modify({ id: cache.identify({ - __typename: addReviewer.object.objectType, - id: addReviewer.object.objectId, + __typename: 'Manuscript', + id: addReviewer.manuscript.id, }), fields: { teams(existingTeamRefs = []) { diff --git a/app/components/component-review/src/components/assignEditors/AssignEditor.js b/app/components/component-review/src/components/assignEditors/AssignEditor.js index 067a3f2ca8..d90c4328bc 100644 --- a/app/components/component-review/src/components/assignEditors/AssignEditor.js +++ b/app/components/component-review/src/components/assignEditors/AssignEditor.js @@ -16,9 +16,8 @@ const teamFields = ` id name role - object { - objectId - objectType + manuscript { + id } members { id @@ -113,8 +112,7 @@ export default compose( props: ({ mutate, ownProps }) => { const createTeam = (userId, teamRole) => { const input = { - objectId: ownProps.manuscript.id, - objectType: 'Manuscript', + manuscriptId: ownProps.manuscript.id, name: teamRole === 'seniorEditor' ? 'Senior Editor' : 'Handling Editor', role: teamRole, diff --git a/app/components/component-review/src/components/decision/DecisionForm.js b/app/components/component-review/src/components/decision/DecisionForm.js index fb0b035a64..6a501deada 100644 --- a/app/components/component-review/src/components/decision/DecisionForm.js +++ b/app/components/component-review/src/components/decision/DecisionForm.js @@ -1,23 +1,19 @@ import React, { useContext } from 'react' import { NoteEditor } from 'xpub-edit' -import { cloneDeep, omit } from 'lodash' +// import { cloneDeep, omit } from 'lodash' import { Field } from 'formik' import { Button, - Flexbox, + // Flexbox, RadioGroup, - UploadButton, - UploadingFile, + // UploadButton, + // UploadingFile, } from '@pubsweet/ui' import { JournalContext } from '../../../../xpub-journal/src' import { required } from '../../../../xpub-validators/src' +import { FilesUpload } from '../../../../shared' -import { - getCommentFiles, - getCommentContent, - stripHtml, - createComments, -} from '../review/util' +import { reviewWithComment } from '../review/util' import { Container, @@ -28,21 +24,45 @@ import { SectionAction, } from '../style' -const NoteDecision = ({ updateReview, uploadFile }) => ( +const NoteDecision = ({ updateReview }) => ( <> - <Field + <Field key="noteField" name="decisionComment"> + {formikBag => ( + <> + <p>NoteDecision: {JSON.stringify(formikBag.form.values)}</p> + <NoteInput updateReview={updateReview} {...formikBag} /> + <FilesUpload + containerId={formikBag.field.value?.id} + containerName="reviewComment" + fieldName="decisionComment.files" + initializeContainer={async () => { + const review = reviewWithComment({ + commentType: 'decision', + isDecision: true, + values: formikBag.form.values, + name: 'decisionComment', + }) + const { data } = await updateReview(review) + return data.updateReview.decisionComment.id + }} + /> + </> + )} + </Field> + {/* <Field component={NoteInput} key="commentinput" name="comments" updateReview={updateReview} validate={required} /> - <Field - component={AttachmentsInput('note')} + <FilesUpload + objectType="Review" + objectId= key="attachmentinput" + fileType="note" updateReview={updateReview} - uploadFile={uploadFile} - /> + /> */} </> ) @@ -51,65 +71,35 @@ const NoteInput = ({ form: { values, setFieldValue }, updateReview, }) => ( - <NoteEditor - key="note-input" - onBlur={() => {}} - onChange={value => { - const { updateIndex, comment } = createComments( - values, - { - type: 'note', - content: stripHtml(value), - }, - 'note', - ) + // const review = useState() - setFieldValue(`comments.${updateIndex}`, comment) - updateReview( - cloneDeep(omit({ comment }, ['comment.files', 'comment.__typename'])), - ) - }} - placeholder="Write/paste your decision letter here, or upload it using the upload button on the right." - value={getCommentContent({ comments: field.value }, 'note')} - /> -) + // const review = useRef({}) -const AttachmentsInput = type => ({ - field, - form: { values, setFieldValue }, - updateReview, - uploadFile, -}) => ( - <> - <UploadButton - buttonText="↑ Upload files" - key="note-attachment" - onChange={event => { - const val = event.target.files[0] - const file = cloneDeep(val) - file.filename = val.name - file.type = type - - const { updateIndex, comment } = createComments( - field.value, - { files: [file] }, - type, - ) + // useEffect(() => { + // review.current = reviewWithComment({ + // id: values.decisionComment?.id, + // values, + // commentType: 'decision', + // name: 'decisionComment', + // isDecision: true, + // }) + // }, [values]) - setFieldValue(`comments.${updateIndex}.files`, comment.files) - - updateReview({}).then(({ data: { updateReview } }) => { - uploadFile(val, updateReview, type) + // console.log('Rendering', review.current) + <> + <NoteEditor + data-testid="decisionComment" + key="note-input" + onBlur={() => {}} + onChange={value => { + // review.current.decisionComment.content = value + updateReview({ + decisionComment: { content: value }, }) }} + placeholder="Write/paste your decision letter here, or upload it using the upload button on the right." + value={field.value?.content || ''} /> - <Flexbox> - {getCommentFiles(field.value, 'note').map(val => { - const file = cloneDeep(val) - file.name = file.filename - return <UploadingFile file={file} key={file.name} uploaded /> - })} - </Flexbox> </> ) @@ -133,14 +123,14 @@ const RecommendationInput = ({ ) } -const DecisionForm = ({ handleSubmit, uploadFile, updateReview, isValid }) => ( +const DecisionForm = ({ handleSubmit, updateReview, isValid }) => ( <Container key="decisionform"> <form onSubmit={handleSubmit}> <SectionHeader> <Title>Decision</Title> </SectionHeader> <SectionRow key="note"> - <NoteDecision updateReview={updateReview} uploadFile={uploadFile} /> + <NoteDecision updateReview={updateReview} /> </SectionRow> <SectionRowGrid> <Field diff --git a/app/components/component-review/src/components/decision/DecisionReviews.js b/app/components/component-review/src/components/decision/DecisionReviews.js index b7bcab3bd4..5e689ae818 100644 --- a/app/components/component-review/src/components/decision/DecisionReviews.js +++ b/app/components/component-review/src/components/decision/DecisionReviews.js @@ -15,7 +15,7 @@ const getCompletedReviews = (manuscript, currentUser) => { if (!team.members) { return null } - const currentMember = team.members.find(m => m.user.id === currentUser.id) + const currentMember = team.members.find(m => m.user?.id === currentUser?.id) return currentMember && currentMember.status } diff --git a/app/components/component-review/src/components/review/Review.js b/app/components/component-review/src/components/review/Review.js index e8e3c65dc6..596489beeb 100644 --- a/app/components/component-review/src/components/review/Review.js +++ b/app/components/component-review/src/components/review/Review.js @@ -3,7 +3,6 @@ import styled from 'styled-components' import { NoteViewer } from 'xpub-edit' import { Attachment } from '@pubsweet/ui' import { th, grid } from '@pubsweet/ui-toolkit' -import { getCommentFiles } from './util' const Heading = styled.div`` const Note = styled.div` @@ -23,17 +22,12 @@ const filesToAttachment = file => ({ url: file.url, }) -const findComments = (review = {}, type) => { - const comments = review.comments || [] - return comments.find(comment => comment.type === type) -} - const ReviewComments = (review, type) => ( <Note> <Content> - <NoteViewer value={findComments(review, type).content} /> + <NoteViewer value={review[`${type}Comment`].content} /> </Content> - {getCommentFiles(review, type).map(attachment => ( + {review[`${type}Comment`].files.map(attachment => ( <Attachment file={filesToAttachment(attachment)} key={attachment.url} @@ -45,14 +39,14 @@ const ReviewComments = (review, type) => ( const Review = ({ review }) => ( <Container> - {findComments(review, 'note') && ( + {review.reviewComment && ( <div> - <Heading>Note</Heading> + <Heading>Review</Heading> - {ReviewComments(review, 'note')} + {ReviewComments(review, 'review')} </div> )} - {findComments(review, 'confidential') && ( + {review.confidentialComment && ( <div> <Heading>Confidential</Heading> diff --git a/app/components/component-review/src/components/review/ReviewForm.js b/app/components/component-review/src/components/review/ReviewForm.js index 006bf2dbef..d681506501 100644 --- a/app/components/component-review/src/components/review/ReviewForm.js +++ b/app/components/component-review/src/components/review/ReviewForm.js @@ -1,17 +1,10 @@ import React, { useContext } from 'react' -import { cloneDeep, set } from 'lodash' import { Field } from 'formik' import { NoteEditor } from 'xpub-edit' -import { - Button, - Flexbox, - RadioGroup, - UploadButton, - UploadingFile, -} from '@pubsweet/ui' +import { Button, RadioGroup } from '@pubsweet/ui' import { JournalContext } from '../../../../xpub-journal/src' -import { getCommentFiles, stripHtml, createComments } from './util' +import { reviewWithComment } from './util' import { AdminSection, Container, @@ -21,46 +14,47 @@ import { SectionRow, SectionAction, } from '../style' +import { FilesUpload } from '../../../../shared' -const AttachmentsInput = ({ - field, - form: { values }, - updateReview, - uploadFile, - type, -}) => ( - <> - <UploadButton - buttonText="↑ Upload files" - onChange={event => { - const val = event.target.files[0] - const file = cloneDeep(val) - file.filename = val.name - file.type = type +// const AttachmentsInput = ({ +// field, +// form: { values }, +// updateReview, +// uploadFile, +// type, +// }) => ( +// <> +// <UploadButton +// buttonText="↑ Upload files" +// onChange={event => { +// const val = event.target.files[0] +// const file = cloneDeep(val) +// file.filename = val.name +// file.type = type - const { updateIndex, comment } = createComments( - values, - { files: [file] }, - type, - ) +// const { updateIndex, comment } = createComments( +// values, +// { files: [file] }, +// type, +// ) - const data = cloneDeep(values) - set(data, `comments.${updateIndex}`, comment) +// const data = cloneDeep(values) +// set(data, `comments.${updateIndex}`, comment) - updateReview(data).then(({ data: { updateReview } }) => { - uploadFile(val, updateReview, type) - }) - }} - /> - <Flexbox> - {getCommentFiles(values, type).map(val => { - const file = cloneDeep(val) - file.name = file.filename - return <UploadingFile file={file} key={file.name} uploaded /> - })} - </Flexbox> - </> -) +// updateReview(data).then(({ data: { updateReview } }) => { +// uploadFile(val, updateReview, type) +// }) +// }} +// /> +// <Flexbox> +// {getCommentFiles(values, type).map(val => { +// const file = cloneDeep(val) +// file.name = file.filename +// return <UploadingFile file={file} key={file.name} uploaded /> +// })} +// </Flexbox> +// </> +// ) const NoteInput = ({ field, @@ -69,26 +63,22 @@ const NoteInput = ({ ...rest }) => ( <NoteEditor + data-testid="reviewComment" key="note-comment" placeholder="Enter your review…" title="Comments to the Author" {...field} onBlur={value => { - const { comment } = createComments( - values, - { - type: 'note', - content: stripHtml(value), - }, - 'note', - ) - - setFieldValue(`comments.0`, comment) - const data = cloneDeep(values) - set(data, `comments.0`, comment) - updateReview(data) + // const review = reviewWithComment({ + // id: values.reviewComment?.id, + // value, + // values, + // commentType: 'review', + // name: 'reviewComment', + // }) + updateReview({ reviewComment: { content: value } }) }} - value={field.value || ''} + value={field.value?.content || ''} /> ) @@ -98,26 +88,22 @@ const ConfidentialInput = ({ updateReview, }) => ( <NoteEditor + data-testid="confidentialComment" key="confidential-comment" placeholder="Enter a confidential note to the editor (optional)…" title="Confidential Comments to Editor (Optional)" {...field} onBlur={value => { - const { comment } = createComments( - values, - { - type: 'confidential', - content: stripHtml(value), - }, - 'confidential', - ) - - setFieldValue(`comments.1`, comment) - const data = cloneDeep(values) - set(data, `comments.1`, comment) - updateReview(data) + // const review = reviewWithComment({ + // id: values.confidentialComment?.id, + // value, + // values, + // commentType: 'confidential', + // name: 'confidentialComment', + // }) + updateReview({ confidentialComment: { content: value } }) }} - value={field.value || ''} + value={field.value?.content || ''} /> ) @@ -127,39 +113,76 @@ const RecommendationInput = ({ field, form: { values }, updateReview }) => { <RadioGroup inline {...field} + data-testid="recommendation" onChange={val => { - const data = cloneDeep(values) - set(data, 'recommendation', val) - updateReview(data) + updateReview({ recommendation: val }) }} options={journal.recommendations} /> ) } -const ReviewComment = props => ( +const ReviewComment = ({ updateReview }) => ( <> <AdminSection> <div name="note"> - <Field key="noteField" name="comments.0.content"> - {extraProps => <NoteInput {...props} {...extraProps} />} - </Field> - <Field - component={extraProps => ( - <AttachmentsInput type="note" {...props} {...extraProps} /> + <Field key="noteField" name="reviewComment"> + {formikBag => ( + <> + <NoteInput updateReview={updateReview} {...formikBag} /> + <FilesUpload + containerId={formikBag.field.value?.id} + containerName="reviewComment" + fieldName="reviewComment.files" + initializeContainer={async () => { + // If the container for the uploaded files is not present, + // we have to create it. InitializeContainer will be called + // if containerId is undefined + const review = reviewWithComment({ + commentType: 'review', + values: formikBag.form.values, + name: 'reviewComment', + }) + // This is an upsert + const { data } = await updateReview(review) + // And we the return the file container id, so + // that we have somewhere to attach uploaded files + return data.updateReview.reviewComment.id + }} + /> + </> )} - /> + </Field> </div> </AdminSection> <AdminSection> <div name="confidential"> - <Field key="confidentialField" name="comments.1.content"> - {extraProps => <ConfidentialInput {...props} {...extraProps} />} - </Field> - <Field - component={extraProps => ( - <AttachmentsInput type="confidential" {...props} {...extraProps} /> + <Field key="confidentialField" name="confidentialComment"> + {formikBag => ( + <> + <ConfidentialInput updateReview={updateReview} {...formikBag} /> + <FilesUpload + containerId={formikBag.field.value?.id} + containerName="reviewComment" + fieldName="confidentialComment.files" + initializeContainer={async () => { + // If the container for the uploaded files is not present, + // we have to create it. InitializeContainer will be called + // if containerId is undefined + const review = reviewWithComment({ + commentType: 'confidential', + values: formikBag.form.values, + name: 'confidentialComment', + }) + // This is an upsert + const { data } = await updateReview(review) + // And we the return the file container id, so + // that we have somewhere to attach uploaded files + return data.updateReview.confidentialComment.id + }} + /> + </> )} - /> + </Field> </div> </AdminSection> </> @@ -181,11 +204,7 @@ const ReviewForm = ({ <Title>Review</Title> </SectionHeader> <SectionRow key="note"> - <ReviewComment - review={review} - updateReview={updateReview} - uploadFile={uploadFile} - /> + <ReviewComment review={review} updateReview={updateReview} /> </SectionRow> <SectionHeader> <Title>Recommendation</Title> diff --git a/app/components/component-review/src/components/review/util.js b/app/components/component-review/src/components/review/util.js index a760b1ba6d..b382a48263 100644 --- a/app/components/component-review/src/components/review/util.js +++ b/app/components/component-review/src/components/review/util.js @@ -4,35 +4,21 @@ export const stripHtml = htmlString => { return temp.textContent } -export const getCommentFiles = (review = {}, type) => { - const comments = - (review.comments || []).find(comment => (comment || {}).type === type) || {} - return comments.files || [] -} - -export const getCommentContent = (review = {}, type) => { - const comments = - (review.comments || []).find(comment => (comment || {}).type === type) || {} - return comments.content || '' -} - -export const createComments = (values, val, type) => { - let updateIndex = (values.comments || []).findIndex( - comment => (comment || {}).type === type, - ) - updateIndex = - (values.comments || []).length > 0 && updateIndex < 0 ? 1 : updateIndex - updateIndex = updateIndex < 0 ? 0 : updateIndex - - const comment = Object.assign( - { - type, - content: '', - files: [], - }, - (values.comments || [])[updateIndex], - val, - ) +export const reviewWithComment = ({ + id, + value, + values, + commentType, + name, + isDecision, +}) => { + const data = { id: values.id } - return { updateIndex, comment } + data.isDecision = isDecision + data[name] = { + id, + commentType, + content: value ? stripHtml(value) : '', + } + return data } diff --git a/app/components/component-review/src/components/reviewers/Reviewers.js b/app/components/component-review/src/components/reviewers/Reviewers.js index 7935205aaf..5ff9ad3024 100644 --- a/app/components/component-review/src/components/reviewers/Reviewers.js +++ b/app/components/component-review/src/components/reviewers/Reviewers.js @@ -43,9 +43,7 @@ const Reviewers = ({ <Heading>Reviewers</Heading> <Button onClick={() => - history.push( - `/journal/versions/${manuscript.id}/decisions/${manuscript.id}`, - ) + history.push(`/journal/versions/${manuscript.id}/decision`) } primary > -- GitLab