Commit 4ea5dd28 authored by Audrey Hamelers's avatar Audrey Hamelers
Browse files

Merge branch 'dev' into 'master'

Dev

Closes #820

See merge request !178
parents c65ec81c f40fdd29
Pipeline #12798 passed with stages
in 49 seconds
......@@ -63,14 +63,16 @@ class GrantSearch extends React.Component {
this.loadFunders(this.props.selectedGrants)
}
}
grantsAdded(grants) {
async grantsAdded(grants) {
if (grants.length > 0) {
await this.props.changedGrants(grants)
this.setState({ grantsErr: '' })
this.props.changedGrants(grants)
this.loadFunders(grants)
} else {
this.props.changedGrants([])
this.props.changedEmbargo(null)
if (this.props.changedEmbargo) {
await this.props.changedEmbargo(null)
}
await this.props.changedGrants([])
this.setState({
grantsErr: 'At least one grant is required.',
embargo: '',
......
......@@ -69,9 +69,7 @@ const IndexedIcon = withTheme(({ theme, indexed }) => (
))
class CitationEdit extends React.Component {
state = {
show: 'search',
}
state = { show: 'search' }
componentDidMount() {
const { manuscript } = this.props
const { journal } = manuscript
......@@ -88,7 +86,7 @@ class CitationEdit extends React.Component {
}
}
render() {
const { manuscript, change, close } = this.props
const { manuscript, change, close, linkAndDelete } = this.props
const { journal, meta } = manuscript
const { notes, articleIds: aids } = meta
const articleIds = aids ? aids.map(aid => omit(aid, '__typename')) : []
......@@ -171,6 +169,7 @@ class CitationEdit extends React.Component {
{(deleteCitation, { data }) => (
<React.Fragment>
<PubMedSearch
adminRemove={linkAndDelete}
citationData={async e => {
await deleteCitation({
variables: {
......@@ -193,6 +192,7 @@ class CitationEdit extends React.Component {
citationData={change}
close={close}
id={manuscript.id}
journal={journal}
meta={meta}
/>
</div>
......
......@@ -62,7 +62,9 @@ const PDay = props => (
const PSeason = props => (
<TextField className={props.className} label="Season" {...props.field} />
)
const EYear = props => <TextField label="Year" {...props.field} />
const EYear = props => (
<TextField invalidTest={props.invalidTest} label="Year" {...props.field} />
)
const EMonth = props => (
<TextField invalidTest={props.invalidTest} label="Month" {...props.field} />
)
......@@ -158,7 +160,14 @@ const CitationForm = ({ values, errors, touched, ...props }) => (
<Flex>
<div>
<H4>Print date</H4>
<Field component={PYear} name="printyear" />
<Field
component={PYear}
invalidTest={errors.printyear && touched.printyear}
name="printyear"
/>
{errors.printyear && touched.printyear && (
<ErrorText>{errors.printyear}</ErrorText>
)}
</div>
<div>
<SmallToggle>
......@@ -207,7 +216,14 @@ const CitationForm = ({ values, errors, touched, ...props }) => (
<Flex>
<div>
<H4>Electronic date</H4>
<Field component={EYear} name="electronicyear" />
<Field
component={EYear}
invalidTest={errors.electronicyear && touched.electronicyear}
name="electronicyear"
/>
{errors.electronicyear && touched.electronicyear && (
<ErrorText>{errors.electronicyear}</ErrorText>
)}
</div>
<div>
<SmallToggle>
......
import moment from 'moment'
import { omit } from 'lodash'
import { compose } from 'recompose'
import { withFormik } from 'formik'
import { graphql } from 'react-apollo'
import { graphql, compose } from 'react-apollo'
import * as yup from 'yup'
import CitationForm from './CitationForm'
import { UPLOAD_CITATION, QUERY_ACTIVITY_INFO } from './operations'
......@@ -16,17 +15,17 @@ const handleSubmit = async (
? prevMeta.articleIds.map(aid => omit(aid, '__typename'))
: []
const meta = {
title: values.title,
volume: values.volume,
issue: values.issue,
title: values.title || null,
volume: values.volume || null,
issue: values.issue || null,
location: {
fpage: values.fpage,
lpage: values.lpage,
elocationId: values.elocationId,
fpage: values.fpage || null,
lpage: values.lpage || null,
elocationId: values.elocationId || null,
},
publicationDates: [],
citerefUrl: values.citeref,
fulltextUrl: values.fulltext,
citerefUrl: values.citeref || null,
fulltextUrl: values.fulltext || null,
}
if (values.printyear) {
const ppub = {
......@@ -143,51 +142,68 @@ const enhancedFormik = withFormik({
fulltext: fulltextUrl || '',
}
},
validationSchema: yup.object().shape({
title: yup.string().required('Title is required'),
volume: yup.string(),
issue: yup.string(),
fpage: yup.string(),
lpage: yup.string(),
elocationId: yup.string(),
doi: yup.string(),
printyear: yup
.date()
.min('2000', 'Invalid year')
.typeError('Invalid year'),
printmonth: yup
.number('')
.integer()
.min(1, 'Invalid month')
.max(12, 'Invalid month')
.typeError('Must be a number'),
printday: yup
.number()
.integer()
.min(1, 'Invalid day')
.max(31, 'Invalid day')
.typeError('Must be a number'),
printseason: yup.string(),
electronicyear: yup
.date()
.min('2000', 'Invalid year')
.typeError('Invalid year'),
electronicmonth: yup
.number()
.integer()
.min(1, 'Invalid month')
.max(12, 'Invalid month')
.typeError('Must be a number'),
electronicday: yup
.number()
.integer()
.min(1, 'Invalid day')
.max(31, 'Invalid day')
.typeError('Must be a number'),
electronicseason: yup.string(),
citeref: yup.string().url('Invalid URL'),
fulltext: yup.string().url('Invalid URL'),
}),
validationSchema: props => {
let { firstYear, endYear } =
props && props.journal ? props.journal.meta : {}
firstYear = firstYear || '2000'
let endYearErrorMessage
if (endYear) {
endYearErrorMessage = `Invalid year, journal end year is ${endYear}`
} else {
endYear = new Date().getFullYear() + 10
endYearErrorMessage =
'Invalid year, journal year is beyond the expected range'
}
return yup.object().shape({
title: yup.string().required('Title is required'),
volume: yup.string(),
issue: yup.string(),
fpage: yup.string(),
lpage: yup.string(),
elocationId: yup.string(),
doi: yup.string(),
printyear: yup
.number()
.min(firstYear, `Invalid year, journal first year is ${firstYear}`)
.max(endYear, endYearErrorMessage)
.typeError('Invalid year'),
printmonth: yup
.number('')
.integer()
.min(1, 'Invalid month')
.max(12, 'Invalid month')
.typeError('Must be a number'),
printday: yup
.number()
.integer()
.min(1, 'Invalid day')
.max(31, 'Invalid day')
.typeError('Must be a number'),
printseason: yup.string(),
electronicyear: yup
.number()
.min(firstYear, `Invalid year, journal first year is ${firstYear}`)
.max(endYear, endYearErrorMessage)
.typeError('Invalid year'),
electronicmonth: yup
.number()
.integer()
.min(1, 'Invalid month')
.max(12, 'Invalid month')
.typeError('Must be a number'),
electronicday: yup
.number()
.integer()
.min(1, 'Invalid day')
.max(31, 'Invalid day')
.typeError('Must be a number'),
electronicseason: yup.string(),
citeref: yup.string().url('Invalid URL'),
fulltext: yup.string().url('Invalid URL'),
})
},
displayName: 'manual-citation',
handleSubmit,
})(CitationForm)
......
......@@ -11,6 +11,7 @@ import ResolveDuplicates from '../ResolveDuplicates'
import CitationEdit from './CitationEdit'
import GrantsEdit from './GrantsEdit'
import EmbargoEdit from './EmbargoEdit'
import { QUERY_ACTIVITY_INFO } from './operations'
const RouteButton = styled(Button)`
align-items: center;
......@@ -49,7 +50,16 @@ const RecoverButton = ({ callback, recoverMan }) => (
const DuplicatesWithMutations = NoteMutations(ResolveDuplicates)
const MetaEdit = withTheme(
({ theme, close, duplicates, lastStatus, manuscript, toEdit, ...props }) => {
({
theme,
close,
duplicates,
lastStatus,
linkAndDelete,
manuscript,
toEdit,
...props
}) => {
const { meta } = manuscript
const { fundingGroup: grants, releaseDelay, notes } = meta
const fundingGroup = grants
......@@ -121,6 +131,7 @@ const MetaEdit = withTheme(
}
}}
close={close}
linkAndDelete={v => linkAndDelete(v)}
manuscript={manuscript}
/>
)
......@@ -137,6 +148,12 @@ const MetaEdit = withTheme(
<SubmissionCancel
callback={close}
manuscriptId={manuscript.id}
refetch={[
{
query: QUERY_ACTIVITY_INFO,
variables: { id: manuscript.id },
},
]}
showComponent={RecoverButton}
/>
</FlexP>
......@@ -236,6 +253,12 @@ const MetaEdit = withTheme(
{` Are you certain? `}
<SubmissionCancel
manuscriptId={manuscript.id}
refetch={[
{
query: QUERY_ACTIVITY_INFO,
variables: { id: manuscript.id },
},
]}
showComponent={DeleteButton}
/>
</span>
......
import React from 'react'
import { Query } from 'react-apollo'
import { Query, compose, graphql } from 'react-apollo'
import { Icon, Action, Button, H2 } from '@pubsweet/ui'
import { th } from '@pubsweet/ui-toolkit'
import styled from 'styled-components'
import moment from 'moment'
import { Notification } from '../ui'
import { CHECK_DUPES } from '../operations'
import MetaEdit from './MetaEdit'
import { QUERY_ACTIVITY_INFO } from './operations'
import { QUERY_ACTIVITY_INFO, LINK_AND_DELETE } from './operations'
const Heading = styled(H2)`
border-bottom: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')};
......@@ -49,7 +50,48 @@ const EditIcon = () => (
)
class MetaSec extends React.Component {
state = { edit: null }
state = { edit: null, linkDelete: null, notif: null }
componentDidMount() {
this._mounted = true
this.notifTimer = null
}
async componentDidUpdate() {
const { notif, linkDelete } = this.state
if (this.notifTimer) {
clearTimeout(this.notifTimer)
}
if (notif && notif.type !== 'error') {
this.notifTimer = setTimeout(() => {
if (this._mounted) {
this.setState({ notif: null })
}
}, 3000)
}
const { manuscript, linkDeleteManuscript } = this.props
const { articleIds } = manuscript.meta
const pmcid =
articleIds &&
articleIds.find(aid => aid.pubIdType === 'pmcid') &&
articleIds.find(aid => aid.pubIdType === 'pmcid').id
if (linkDelete && linkDelete === pmcid) {
const { data } = await linkDeleteManuscript({
variables: { id: manuscript.id },
})
const result = data.linkDeleteManuscript
;(() =>
this.setState({
linkDelete: null,
notif: {
type: result.success ? 'success' : 'error',
message: result.message,
},
}))()
}
}
componentWillUnmount() {
this._mounted = false
clearTimeout(this.notifTimer)
}
render() {
const { manuscript } = this.props
const { teams, audits, meta, journal } = manuscript
......@@ -66,6 +108,8 @@ class MetaSec extends React.Component {
} = meta
let duplicates = []
const pmid = articleIds && articleIds.find(aid => aid.pubIdType === 'pmid')
const pmcid =
articleIds && articleIds.find(aid => aid.pubIdType === 'pmcid')
const submitter = teams.find(team => team.role === 'submitter')
const { title: st, givenNames: sg, surname: ss } = submitter
? submitter.teamMembers[0].alias.name
......@@ -96,9 +140,15 @@ class MetaSec extends React.Component {
publicationDates.find(d => d.type === 'ppub').date,
).year())) ||
null
const { edit } = this.state
const { edit, notif } = this.state
return (
<React.Fragment>
{notif && (
<React.Fragment>
<br />
<Notification type={notif.type}>{notif.message}</Notification>
</React.Fragment>
)}
<DL style={{ float: 'right', marginTop: '1rem' }}>
<dt>Status:</dt>
<dd>
......@@ -191,7 +241,8 @@ class MetaSec extends React.Component {
`:${location.elocationId}.`}
</React.Fragment>
)}
{pmid && ` PMID: ${pmid.id}`}
{pmid && ` PMID: ${pmid.id}.`}
{pmcid && ` PMCID: ${pmcid.id}.`}
<Action onClick={() => this.setState({ edit: 'citation' })}>
<EditIcon />
</Action>
......@@ -235,6 +286,7 @@ class MetaSec extends React.Component {
lastStatusChange.originalData &&
lastStatusChange.originalData.status
}
linkAndDelete={v => this.setState({ linkDelete: v })}
manuscript={manuscript}
refetch={[
{
......@@ -255,4 +307,13 @@ class MetaSec extends React.Component {
}
}
export default MetaSec
export default compose(
graphql(LINK_AND_DELETE, {
name: 'linkDeleteManuscript',
options: props => ({
refetchQueries: [
{ query: QUERY_ACTIVITY_INFO, variables: { id: props.manuscript.id } },
],
}),
}),
)(MetaSec)
......@@ -38,6 +38,15 @@ export const DELETE_CITATION = gql`
}
`
export const LINK_AND_DELETE = gql`
mutation LinkAndDelete($id: ID!) {
linkDeleteManuscript(id: $id) {
success
message
}
}
`
export const QUERY_ACTIVITY_INFO = gql`
query QueryActivitiesByManuscriptId($id: ID!) {
activities: epmc_queryActivitiesByManuscriptId(id: $id) {
......
......@@ -35,6 +35,17 @@ const Links = styled.p`
align-items: center;
}
`
const FlexP = styled.p`
display: flex;
align-items: center;
& > button {
flex: 0 0 auto;
}
& > span {
flex: 1 1 50%;
margin-left: ${th('gridUnit')};
}
`
const isDate = d => {
let date = moment.utc(d, 'YYYY MMM DD')
if (!date.isValid()) {
......@@ -56,7 +67,6 @@ class PubMedSearch extends React.Component {
loading: false,
hitcount: null,
inPMC: null,
pmcid: null,
inEPMC: false,
unmatched: false,
pubProvided: false,
......@@ -108,6 +118,12 @@ class PubMedSearch extends React.Component {
}
selectResult(result, journal) {
const publicationDates = []
const articleIds = [
{
pubIdType: 'pmid',
id: result.uid,
},
]
if (result.pubdate) {
const ppub = { type: 'ppub' }
if (isDate(result.pubdate)) {
......@@ -134,6 +150,20 @@ class PubMedSearch extends React.Component {
}
publicationDates.push(epub)
}
if (result.articleids) {
const doi = result.articleids.find(i => i.idtype === 'doi')
const pmc = result.articleids.find(i => i.idtype === 'pmc')
doi &&
articleIds.push({
pubIdType: 'doi',
id: doi.value,
})
pmc &&
articleIds.push({
pubIdType: 'pmcid',
id: pmc.value,
})
}
const citationData = {
journalId: journal.id,
meta: {
......@@ -147,20 +177,9 @@ class PubMedSearch extends React.Component {
elocationId: result.elocationid,
},
publicationDates,
articleIds: [
{
pubIdType: 'pmid',
id: result.uid,
},
],
articleIds,
},
}
if (result.articleids && result.articleids.find(i => i.idtype === 'doi')) {
citationData.meta.articleIds.push({
pubIdType: 'doi',
id: result.articleids.find(i => i.idtype === 'doi').value,
})
}
this.props.citationData(citationData)
}
onQueryChange(event) {
......@@ -183,7 +202,10 @@ class PubMedSearch extends React.Component {
const { query, retstart, results } = this.state
if (retstart >= results.length && query) {
const SearchUrl = `/eutils/esearch?term=${query}&retstart=${retstart}&db=pubmed`
const SearchUrl = `/eutils/esearch?term=${query.replace(
/(.*)\W$/g,
'$1',
)}&retstart=${retstart}&db=pubmed`
const response = await fetch(SearchUrl, {
headers: new Headers({
......@@ -214,12 +236,13 @@ class PubMedSearch extends React.Component {
this.setState({ loading: false })
}
async onSelected(result, journal) {
const inPMC = result.articleids.find(id => id.idtype === 'pmc')
if (inPMC) {
const pmcid =
result.articleids.find(id => id.idtype === 'pmc') &&
result.articleids.find(id => id.idtype === 'pmc').value
if (pmcid) {
this.setState({ loading: true })
const obj = {
inPMC: result.title,
pmcid: inPMC.value,
inPMC: { pmcid, result, journal },
loading: false,
}
const epmcURL = `https://www.ebi.ac.uk/europepmc/webservices/rest/search?query=SRC:MED%20EXT_ID:${
......@@ -249,10 +272,9 @@ class PubMedSearch extends React.Component {
inPMC,
inEPMC,
pubProvided,
pmcid,
unmatched,
} = this.state
const { manuscript, toggle } = this.props
const { manuscript, toggle, adminRemove } = this.props
const { meta, journal } = manuscript || {}
const { notes } = meta || {}
const note = notes ? notes.find(n => n.notesType === 'userCitation') : null
......@@ -297,28 +319,63 @@ class PubMedSearch extends React.Component {
<H3>No further action is required for this manuscript</H3>
<p>
The full text of the article &apos;
<HTMLString string={inPMC} />
<HTMLString string={inPMC.result.title} />
&apos;
{` has already been sent to Europe PMC. Please use `}
<B>{pmcid}</B>
<B>{inPMC.pmcid}</B>
{` for grant reporting purposes.`}
</p>
{inEPMC && (
<Links>
<A href={`https://europepmc.org/articles/${pmcid}`}>
<A
href={`https://europepmc.org/articles/${inPMC.pmcid}`}
>
<Icon color="currentColor">arrow-right-circle</Icon>
View this article on Europe PMC
</A>
{/* TODO: ORCID claiming
<br />
<A