diff --git a/app/components/dashboard/AdminDashboard.jsx b/app/components/dashboard/AdminDashboard.jsx index 7865400c856200b3c10787466dc684b08ed5992a..52c007ba82b8438b049f6788b1669a815e5a0576 100755 --- a/app/components/dashboard/AdminDashboard.jsx +++ b/app/components/dashboard/AdminDashboard.jsx @@ -34,8 +34,8 @@ const TaggerQueue = { tagging: ['tagging'] } const Completed = { 'approved for PMC': ['xml-complete'], published: ['published'], - removed: ['removed'], - withdrawn: ['withdrawn'], + 'being withdrawn': ['being-withdrawn'], + deleted: ['deleted'], } const QueueTable = ({ title, queue, data }) => { diff --git a/app/components/dashboard/DashboardList.jsx b/app/components/dashboard/DashboardList.jsx index d6841119f15f2a3793abaaffc8fa849df1f7bf05..8bf123087003c4f23e5858a2898e829163546573 100644 --- a/app/components/dashboard/DashboardList.jsx +++ b/app/components/dashboard/DashboardList.jsx @@ -313,8 +313,8 @@ export const States = { admin: adminState, } -const timeSince = date => { - const seconds = Math.floor((new Date() - date) / 1000) +export const timeSince = date => { + const seconds = Math.floor((new Date() - new Date(date)) / 1000) let interval = Math.floor(seconds / 31536000) if (interval >= 1) { return `${interval} year${interval !== 1 ? 's' : ''}` @@ -401,7 +401,7 @@ const DashboardList = ({ </div> <Column> <ItemStatus className={state.color}>{state.status}</ItemStatus> - <B>{timeSince(new Date(updated))}</B> + <B>{timeSince(updated)}</B> {userRole === 'admin' && claimStates.includes(status) && ( <React.Fragment> {claimedBy ? ( diff --git a/app/components/dashboard/index.js b/app/components/dashboard/index.js index c0a503e41a92494d77246d0e7c6686256b29c6d6..c79cb9aa9d4c0c590af53f71cd3dc75991f1e54a 100644 --- a/app/components/dashboard/index.js +++ b/app/components/dashboard/index.js @@ -1,4 +1,4 @@ export { default as SearchResultsPage } from './SearchResultsPage' export { default as DashboardPage } from './DashboardPage' -export { States } from './DashboardList' +export { States, timeSince } from './DashboardList' export { default as AdminDashboard } from './AdminDashboard' diff --git a/app/components/submission-wizard/HandleDuplicates.jsx b/app/components/submission-wizard/HandleDuplicates.jsx deleted file mode 100644 index 1ce0313c735676e7ebecdacbec25a5989884d2b8..0000000000000000000000000000000000000000 --- a/app/components/submission-wizard/HandleDuplicates.jsx +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react' -import { withRouter } from 'react-router' -import { Mutation } from 'react-apollo' -import styled from 'styled-components' -import { H3, Button, Action, Icon, Link } from '@pubsweet/ui' -import { B, Buttons, Close, CloseButton, Portal } from '../ui' -import { States } from '../dashboard' -import Citation from './Citation' -import { REPLACE_MANUSCRIPT, CHECK_DUPES } from './operations' - -const Columns = styled.div` - display: flex; - align-items: flex-start; - justify-content: space-evenly; - h3 { - margin-top: 0; - } - button { - display: flex; - align-items: center; - } -` -const HandleDuplicates = ({ close, current, duplicates }) => ( - <Mutation - mutation={REPLACE_MANUSCRIPT} - refetchQueries={result => { - if (result.data.replaceManuscript) { - return [ - { - query: CHECK_DUPES, - variables: { - id: current.id, - pmid: - current.meta.articleIds && - current.meta.articleIds.find(id => id.pubIdType === 'pmid') - ? current.meta.articleIds.find(id => id.pubIdType === 'pmid') - .id - : null, - title: current.meta.title, - }, - }, - ] - } - return [] - }} - > - {(replaceManuscript, { data }) => { - const replace = async (keepId, throwId) => { - const { data } = await replaceManuscript({ - variables: { keepId, throwId }, - }) - if (data.replaceManuscript && throwId === current.id) { - this.props.history.push('/') - } - } - return ( - <Portal transparent> - <Close> - <CloseButton onClick={close} /> - </Close> - <Columns> - <div> - <H3>This manuscript</H3> - <Citation journal={current.journal} metadata={current.meta} /> - <p> - <B>Status:</B> {current.status} - </p> - <p> - <B>Grants: </B> - {current.meta.fundingGroup.map((f, t) => ( - <span key={f.awardId}> - {`${f.fundingSource} ${f.awardId}`} - {t !== current.meta.fundingGroup.length - 1 && ', '} - </span> - ))} - </p> - <Action onClick={() => replace(current.id, duplicates[0].id)}> - Remove this & transfer grants - <Icon color="currentColor">arrow-right</Icon> - </Action> - </div> - <div> - <H3>Duplicate(s)</H3> - {duplicates.map(dupe => ( - <React.Fragment> - <Link - target="_blank" - to={`/submission/${dupe.id}/${ - States.admin[dupe.status].url - }`} - > - <Citation journal={dupe.journal} metadata={dupe.meta} /> - </Link> - <p> - <B>Status:</B> {dupe.status} - </p> - <p> - <B>Grants: </B> - {dupe.meta.fundingGroup.map((f, t) => ( - <span key={f.awardId}> - {`${f.fundingSource} ${f.awardId}`} - {t !== dupe.meta.fundingGroup.length - 1 && ', '} - </span> - ))} - </p> - <Action onClick={() => replace(current.id, dupe.id)}> - <Icon color="currentColor">arrow-left</Icon> - Transfer grants & remove this - </Action> - </React.Fragment> - ))} - </div> - </Columns> - <Buttons> - <Button onClick={close}>Exit</Button> - </Buttons> - </Portal> - ) - }} - </Mutation> -) - -export default withRouter(HandleDuplicates) diff --git a/app/components/submission-wizard/ResolveDuplicates.jsx b/app/components/submission-wizard/ResolveDuplicates.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f4b2ee281a3d3496469e3c5254ed46f839a59130 --- /dev/null +++ b/app/components/submission-wizard/ResolveDuplicates.jsx @@ -0,0 +1,179 @@ +import React from 'react' +import { withRouter } from 'react-router' +import { Mutation } from 'react-apollo' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { H3, H4, Button, Action, Icon, Link } from '@pubsweet/ui' +import { B, Buttons, Close, CloseButton, Portal, Notification } from '../ui' +import { States, timeSince } from '../dashboard' +import Citation from './Citation' +import { REPLACE_MANUSCRIPT, CHECK_DUPES } from './operations' + +const Columns = styled.div` + display: flex; + flex-wrap: wrap; + align-items: stretch; + justify-content: flex-end; + h3 { + margin-top: 0; + } + h4 { + margin: 0; + } + button { + display: flex; + align-items: center; + } + & > * { + width: 48%; + box-sizing: border-box; + margin-right: 1%; + margin-left: 1%; + } + & > div { + margin-top: ${th('gridUnit')}; + display: flex; + align-items: stretch; + justify-content: space-between; + flex-direction: column; + border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + padding: calc(${th('gridUnit')} * 2); + } +` +const Current = styled.div` + background-color: ${th('colorBackground')}; +` +const Dupes = styled.div` + background-color: ${th('colorBackgroundHue')}; +` +const FlexP = styled.div` + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; +` +const ResolveDuplicates = ({ close, current, duplicates }) => ( + <Mutation + mutation={REPLACE_MANUSCRIPT} + refetchQueries={result => { + if (result.data.replaceManuscript) { + return [ + { + query: CHECK_DUPES, + variables: { + id: current.id, + pmid: + current.meta.articleIds && + current.meta.articleIds.find(id => id.pubIdType === 'pmid') + ? current.meta.articleIds.find(id => id.pubIdType === 'pmid') + .id + : null, + title: current.meta.title, + }, + }, + ] + } + return [] + }} + > + {(replaceManuscript, { data }) => { + const replace = async (keepId, throwId) => { + const { data } = await replaceManuscript({ + variables: { keepId, throwId }, + }) + if (data.replaceManuscript && throwId === current.id) { + this.props.history.push('/') + } + } + console.log(duplicates) + return ( + <Portal transparent> + <Close> + <CloseButton onClick={close} /> + </Close> + <Columns> + <H3>This manuscript</H3> + <H3>Duplicate(s)</H3> + <p style={{ flex: '0 0 100%' }}> + <Notification type="error"> + Two articles cannot have the same PMID. + </Notification> + </p> + <Current> + <H4>{current.id}</H4> + <Citation journal={current.journal} metadata={current.meta} /> + <FlexP> + <span> + <B>Status:</B> {current.status} + </span> + <span> + <B>Last updated:</B> {timeSince(current.updated)} + </span> + </FlexP> + <p> + <B>Grants: </B> + {current.meta.fundingGroup && current.meta.fundingGroup.map((f, t) => ( + <span key={f.awardId}> + {`${f.fundingSource} ${f.awardId}`} + {t !== current.meta.fundingGroup.length - 1 && ', '} + </span> + ))} + </p> + <Button onClick={() => replace(current.id, duplicates[0].id)} primary> + Remove this & transfer grants + <Icon color="currentColor">arrow-right</Icon> + </Button> + </Current> + {duplicates.map((dupe, i) => ( + <React.Fragment> + <Dupes key={dupe.id}> + <H4>{dupe.id}</H4> + <Link + target="_blank" + to={`/submission/${dupe.id}/${ + States.admin[dupe.status].url + }`} + > + <Citation journal={dupe.journal} metadata={dupe.meta} /> + </Link> + <FlexP> + <span> + <B>Status:</B> {dupe.status} + </span> + <span> + <B>Last updated:</B> {timeSince(dupe.updated)} + </span> + </FlexP> + <p> + <B>Grants: </B> + {dupe.meta.fundingGroup && dupe.meta.fundingGroup.map((f, t) => ( + <span key={f.awardId}> + {`${f.fundingSource} ${f.awardId}`} + {t !== dupe.meta.fundingGroup.length - 1 && ', '} + </span> + ))} + </p> + <Button onClick={() => replace(current.id, dupe.id)} primary> + <Icon color="currentColor">arrow-left</Icon> + Transfer grants & remove this + </Button> + </Dupes> + {i === 0 && ( + <p style={{ flex: '0 0 100%', textAlign: 'center' }}> + <Button style={{ margin: '0 auto' }}>These two are not duplicates!</Button> + </p> + )} + {i !== duplicates.length - 1 && <br />} + </React.Fragment> + ))} + </Columns> + <Buttons> + <Button onClick={close}>Exit</Button> + </Buttons> + </Portal> + ) + }} + </Mutation> +) + +export default withRouter(ResolveDuplicates) diff --git a/app/components/submission-wizard/Submit.jsx b/app/components/submission-wizard/Submit.jsx index a297be0c36d9273495e80739d0bc5377b11c6f73..a1a6d3463f5af5f35b944044f269de5507fa6ffa 100644 --- a/app/components/submission-wizard/Submit.jsx +++ b/app/components/submission-wizard/Submit.jsx @@ -19,7 +19,7 @@ import { } from '../ui' import Citation from './Citation' import GrantSearch from './GrantSearch' -import HandleDuplicates from './HandleDuplicates' +import ResolveDuplicates from './ResolveDuplicates' import SelectReviewer from './SelectReviewer' import SubmitForm from './SubmitForm' import SubmitHighlights from './SubmitHighlights' @@ -169,7 +169,7 @@ class Submit extends React.Component { onClick={() => this.setState({ prune: true })} primary > - Handle duplicates + Resolve duplicates </Button> <ErrorMessage> <Icon color="currentColor" size={2}> @@ -367,7 +367,7 @@ class Submit extends React.Component { <div> <PanelHeader> <H2> - {status === 'submission-error' ? 'Edit' : 'Confirm'} + {status === 'submission-error' && 'Edit' || status === 'submitted' && 'Review' || 'Confirm'} {` input & approve`} </H2> </PanelHeader> @@ -410,7 +410,7 @@ class Submit extends React.Component { </div> </EditPanel> {duplicates && prune && ( - <HandleDuplicates + <ResolveDuplicates close={() => this.setState({ prune: false })} current={manuscript} duplicates={duplicates} diff --git a/app/components/submission-wizard/SubmitPage.jsx b/app/components/submission-wizard/SubmitPage.jsx index 3eceddbdfea9bbbe1d09571f59da8c0c6df65318..f26226bd43d1c6b750e689342b70492a7d261245 100755 --- a/app/components/submission-wizard/SubmitPage.jsx +++ b/app/components/submission-wizard/SubmitPage.jsx @@ -27,7 +27,7 @@ const DupeCheckSubmitPage = ({ manuscript, ...props }) => { ) } const duplicates = - data.checkDuplicates.length > 0 && data.checkDuplicates + data.checkDuplicates.length > 0 && data.checkDuplicates || null return ( <SubmitWithHeader duplicates={duplicates} diff --git a/app/components/submission-wizard/operations.js b/app/components/submission-wizard/operations.js index 580654bafe9b7bf91867674c74c48efdcc3b2aca..13ebd2bd2e5c53a1a2f0c0bec4a5e966fdfc64fa 100644 --- a/app/components/submission-wizard/operations.js +++ b/app/components/submission-wizard/operations.js @@ -20,6 +20,7 @@ const ManuscriptFragment = gql` fragment ManuscriptFragment on Manuscript { id status + updated formState journal { journalTitle diff --git a/config/authsome.js b/config/authsome.js index d332f54c5580bbad5533cba163bc997d23f7085b..a24d72ce6335de13ab6eeea5f22075f3fa86824b 100755 --- a/config/authsome.js +++ b/config/authsome.js @@ -28,8 +28,8 @@ const authorisationMap = { Manuscript: { roles: { admin: states, - submitter: states.slice(0, states.length - 2), - reviewer: states.slice(0, states.length - 2), + submitter: states.slice(0, states.length - 1), + reviewer: states.slice(0, states.length - 1), tagger: ['tagging'], 'external-admin': ['xml-qa'], }, diff --git a/config/states.json b/config/states.json index b75a41175c4fcb933740a1d4eeda6bc20f040f84..7be3a7ccc976cbd1185fa77d7a61931509ed1a10 100644 --- a/config/states.json +++ b/config/states.json @@ -11,6 +11,5 @@ "xml-complete", "ncbi-ready", "published", - "removed", - "withdrawn" + "being-withdrawn" ] \ No newline at end of file diff --git a/server/email/templates/rejectSubmission.js b/server/email/templates/rejectSubmission.js index 49f289c01e7d250e48c3d3dc37fded4ecc15afbe..63ef20b6c59ef78063ca70e59d4b4e3ed2149873 100644 --- a/server/email/templates/rejectSubmission.js +++ b/server/email/templates/rejectSubmission.js @@ -10,7 +10,7 @@ const rejectSubmissionTemplate = ( <p>Your manuscript submission, <b>${title}</b> was rejected by the reviewer.<p> <p>The reviewer, ${reviewer}, sent the following message:</p> <blockquote style="white-space: pre-wrap">${message}</blockquote> - <p>Please go to your manuscript at <a style="color:#20699C" href="${link}"></a> to correct any errors in the submission.</p> + <p>Please go to your manuscript at <a style="color:#20699C" href="${link}">${link}</a> to correct any errors in the submission.</p> <p>Kind regards,</p> <p>The Europe PMC plus system</p> ` diff --git a/server/xpub-model/entities/manuscript/data-access.js b/server/xpub-model/entities/manuscript/data-access.js index eb7a6d1e0d6322bab62f67d19280718db3312ee2..1bc7a8049d2a2329b6bc02a958d48b99a0c73b83 100644 --- a/server/xpub-model/entities/manuscript/data-access.js +++ b/server/xpub-model/entities/manuscript/data-access.js @@ -219,10 +219,10 @@ class Manuscript extends EpmcBaseModel { static async selectById(id, eager = false) { let manuscripts if (eager) { + // don't whereNull deleted here manuscripts = await Manuscript.query() .select('manuscript.*') .where('manuscript.id', id) - .whereNull('manuscript.deleted') .eager('[journal, teams.users, notes, files, claiming]') .modifyEager('teams', builder => { builder.whereNull('deleted') @@ -271,6 +271,33 @@ class Manuscript extends EpmcBaseModel { return parseInt(count[0].count, 10) } + static async countDeleted() { + const count = await knex('manuscript') + .count('*') + .whereNotNull('deleted') + return parseInt(count[0].count, 10) + } + + static async getDeleted(page = 0, pageSize = PAGE_SIZE, user) { + const manuscripts = + page === -1 + ? await Manuscript.query() + .distinct('manuscript.id') + .select('manuscript.*') + .eager('[teams.users, claiming, journal]') + .whereNotNull('manuscript.deleted') + .orderBy('manuscript.updated', 'desc') + : await Manuscript.query() + .distinct('manuscript.id') + .select('manuscript.*') + .eager('[teams.users, claiming, journal]') + .whereNotNull('manuscript.deleted') + .orderBy('manuscript.updated', 'desc') + .page(page, pageSize) + logger.debug('manuscripts: ', manuscripts) + return manuscripts + } + static async selectActivityById(id) { const manuscripts = await Manuscript.query() .where('manuscript.id', id) diff --git a/server/xpub-model/entities/manuscript/index.js b/server/xpub-model/entities/manuscript/index.js index f2d2bbaf4b2c1ecfbee27eec7006376adb44dd68..028945587f40066fb170362a560e142d9d28fe8d 100755 --- a/server/xpub-model/entities/manuscript/index.js +++ b/server/xpub-model/entities/manuscript/index.js @@ -33,12 +33,10 @@ const Manuscript = { }, findByStatus: async (query, page, pageSize, user) => { - const manuscripts = await ManuscriptAccess.selectByStatus( - query, - page, - pageSize, - user, - ) + const manuscripts = + query === 'deleted' + ? await ManuscriptAccess.getDeleted(page, pageSize, user) + : await ManuscriptAccess.selectByStatus(query, page, pageSize, user) const rtn = page === -1 ? manuscripts.map(manuscript => gManuscript(manuscript)) @@ -113,6 +111,8 @@ const Manuscript = { const count = await ManuscriptAccess.countByStatus(type) return { type, count } }) + const deleted = await ManuscriptAccess.countDeleted() + counts.push({ type: 'deleted', count: deleted }) return counts }, @@ -151,26 +151,16 @@ const Manuscript = { findByDepositStatesNull: ManuscriptAccess.selectByPdfDepositStatesNull, delete: async (id, userId) => { - let trx try { - trx = await transaction.start(ManuscriptAccess.knex()) const manuscript = await ManuscriptAccess.selectById(id) if (manuscript) { - await ManuscriptAccess.delete(id, userId, trx) - await FileAccess.deleteByManuscriptId(id, userId, trx) - await ReviewAccess.deleteByManuscriptId(id, userId, trx) - await NoteAccess.deleteByManuscriptId(id, userId, trx) - await trx.commit() + await ManuscriptAccess.delete(id, userId) return true } return false } catch (error) { - if (trx) { - await trx.rollback() - } - logger.error('Nothing was deleted') - logger.error(error) - throw error + logger.error(`Nothing was deleted: ${error}`) + return false } }, @@ -278,7 +268,7 @@ const Manuscript = { lodash.assign(keepMan, input) try { await keepMan.save() - await Manuscript.delete(throwId, userId) + await ManuscriptAccess.delete(throwId, userId) await removeDuplicateEmail(user, throwMan, keepMan) } catch (error) { logger.error(error)