Commit ee035639 authored by Yuci Gou's avatar Yuci Gou

Merge branch 'dev' of gitlab.coko.foundation:xpub/xpub-epmc into dev

parents 2679a6b7 53bd890a
Pipeline #13421 failed with stages
in 32 seconds
......@@ -122,7 +122,7 @@
ol, ul {
margin-left: 1em;
padding-left: 1em;
padding-left: 1.1em;
}
li {
......@@ -1717,7 +1717,8 @@
}
.list-label{
display: inline-block;
text-indent: -1.1em;
margin-left: -1.2em;
padding-right: .1em;
}
.super,
.fn-label,
......
......@@ -188,7 +188,7 @@ class GrantSearch extends React.Component {
<H2>Funding</H2>
<H3>Add all grants that support this manuscript</H3>
<SearchSelect
displaySelected="`${option.fundingSource}, ${option.awardId} (${option.pi.surname})`"
displaySelected="`${option.fundingSource}, ${option.awardId} (${option.pi.surname}): ${option.title}`"
icon="chevron_down"
invalidTest={this.state.grantsErr}
label="Search by Grant # or by PI surname followed by initial(s), e.g. Smith A"
......
import React from 'react'
import { Icon } from '@pubsweet/ui'
import { th, lighten } from '@pubsweet/ui-toolkit'
import styled from 'styled-components'
import { notification } from 'config'
import moment from 'moment'
import { Notification } from './ui'
const Christmas = styled(Notification)`
background-color: ${lighten('colorWarning', 70)};
div {
display: flex;
align-items: flex-start;
.text {
margin-left: ${th('gridUnit')};
}
}
`
const start = moment(notification.holiday.start).format('dddd, D MMMM')
const end = moment(notification.holiday.end).format('dddd, D MMMM')
const Holiday = props => (
<Christmas>
<Icon color="green" size={2.5}>
gift
</Icon>
<Icon color="#a80202" size={2.5}>
gift
</Icon>
<span className="text">
Please note that the Helpdesk will be unavailable from {start} to {end}.
Any calls or e-mails will be saved and responded to on our return. Happy
Holidays from the Europe PMC team.{' '}
</span>
</Christmas>
)
export default Holiday
import React from 'react'
import { omit } from 'lodash'
import styled, { withTheme } from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import { Action, H2, Button, Icon } from '@pubsweet/ui'
import { states } from 'config'
import { Portal, Buttons, CloseModal, Notification } from '../ui'
import { withTheme } from 'styled-components'
import { Button } from '@pubsweet/ui'
import { Portal, Buttons, CloseModal } from '../ui'
import { ManuscriptMutations, NoteMutations } from '../SubmissionMutations'
import SubmissionCancel from '../SubmissionCancel'
import ResolveDuplicates from '../ResolveDuplicates'
import ReviewerEdit from './ReviewerEdit'
import CitationEdit from './CitationEdit'
import GrantsEdit from './GrantsEdit'
import EmbargoEdit from './EmbargoEdit'
import StatusEdit from './StatusEdit'
import { QUERY_ACTIVITY_INFO } from './operations'
const RouteButton = styled(Button)`
align-items: center;
display: inline-flex;
`
const FlexP = styled.p`
display: flex;
align-items: center;
& > button {
flex: 0 0 auto;
}
& > span {
flex: 1 1 50%;
margin-left: ${th('gridUnit')};
}
`
export const Exit = ({ close }) => (
<Buttons right>
<Button onClick={close}>Exit</Button>
</Buttons>
)
const DeleteButton = ({ deleteMan }) => (
<Action onClick={() => deleteMan()}>Confirm deletion</Action>
)
const RecoverButton = ({ callback, recoverMan }) => (
<RouteButton onClick={() => recoverMan(callback())}>
<Icon color="currentColor" size={2.5}>
refresh-cw
</Icon>
Recover manuscript
</RouteButton>
)
const DuplicatesWithMutations = NoteMutations(ResolveDuplicates)
const ReviewerWithMutations = NoteMutations(ReviewerEdit)
......@@ -72,10 +42,7 @@ const MetaEdit = withTheme(
return n
})
: []
const qa = states.indexOf('submitted')
const xml = states.indexOf('xml-triage')
const done = states.indexOf('ncbi-ready')
const curr = states.indexOf(manuscript.status)
if (toEdit === 'dupes' && duplicates) {
return (
<DuplicatesWithMutations
......@@ -154,147 +121,18 @@ const MetaEdit = withTheme(
}
}}
close={close}
linkAndDelete={v => linkAndDelete(v)}
linkAndDelete={linkAndDelete}
manuscript={manuscript}
/>
)
case 'status':
return (
<React.Fragment>
<H2>Send/Remove</H2>
{manuscript.deleted ? (
<React.Fragment>
<Notification type="info">
{`Manuscript succesfully deleted. If not already sent, please send a message to the user(s) explaining the deletion.`}
</Notification>
<FlexP>
<SubmissionCancel
callback={close}
manuscriptId={manuscript.id}
refetch={[
{
query: QUERY_ACTIVITY_INFO,
variables: { id: manuscript.id },
},
]}
showComponent={RecoverButton}
/>
</FlexP>
</React.Fragment>
) : (
<React.Fragment>
{manuscript.status === 'submission-error' ? (
<FlexP>
<RouteButton
onClick={() =>
props.setStatus(lastStatus || 'submitted')
}
>
<Icon color="currentColor" size={2.5}>
check-circle
</Icon>
Cancel submission error
</RouteButton>
<span>
Cancel submission error request and send back to
{(lastStatus &&
lastStatus === 'xml-triage' &&
` XML errors state ('xml-triage')`) ||
(lastStatus &&
lastStatus === 'in-review' &&
` Initial review state (in-review)`) ||
` QA state ('submitted')`}
</span>
</FlexP>
) : (
<React.Fragment>
{lastStatus && (
<FlexP>
<RouteButton
onClick={() =>
props.setStatus(lastStatus, close)
}
>
<Icon color="currentColor" size={2.5}>
skip-back
</Icon>
Send back
</RouteButton>
<span>
to previous status of submission: {lastStatus}
<br />
Note that no emails will be automatically
sent!
</span>
</FlexP>
)}
<FlexP>
<RouteButton
disabled={!(curr > qa && curr !== xml)}
onClick={() =>
props.setStatus(
curr > xml ? 'xml-triage' : 'submitted',
close,
)
}
>
<Icon color="currentColor" size={2.5}>
send
</Icon>
Send for routing
</RouteButton>
{curr > qa && curr !== xml ? (
<span>
{` send to most recent routable status:`}
{curr > xml ? ' xml-triage' : ' submitted'}
</span>
) : (
<span>
{` The submission is already in a routable state (`}
{manuscript.status}
{`). You may send it backward or forward from the manuscript view.`}
</span>
)}
</FlexP>
</React.Fragment>
)}
<FlexP>
<RouteButton
disabled={curr >= done}
onClick={e =>
e.currentTarget.nextElementSibling.classList.remove(
'hidden',
)
}
>
<Icon color="currentColor" size={2.5}>
trash-2
</Icon>
Delete manuscript
</RouteButton>
<span className="hidden">
{` Are you certain? `}
<SubmissionCancel
manuscriptId={manuscript.id}
refetch={[
{
query: QUERY_ACTIVITY_INFO,
variables: { id: manuscript.id },
},
]}
showComponent={DeleteButton}
/>
</span>
{curr >= done && (
<span>
{` Manuscript has been sent to PMC and must be withdrawn.`}
</span>
)}
</FlexP>
</React.Fragment>
)}
<Exit close={close} />
</React.Fragment>
<StatusEdit
close={close}
lastStatus={lastStatus}
manuscript={manuscript}
setStatus={props.setStatus}
/>
)
default:
return null
......
......@@ -65,7 +65,7 @@ const DupeButton = styled(Button)`
padding: calc(${th('gridUnit')} / 2) ${th('gridUnit')};
font-size: ${th('fontSizeBaseSmall')};
`
const EditIcon = () => (
export const EditIcon = () => (
<Icon color="currentColor" size={1.9} style={{ verticalAlign: 'bottom' }}>
edit-3
</Icon>
......@@ -100,17 +100,18 @@ class MetaSec extends React.Component {
linkDelete === pmcid &&
manuscript.status !== 'xml-complete'
) {
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,
},
this.setState({ linkDelete: null }, async () => {
const { data } = await linkDeleteManuscript({
variables: { id: manuscript.id },
})
const result = data.linkDeleteManuscript
this.setState({
notif: {
type: result.success ? 'success' : 'error',
message: result.message,
},
})
}))()
}
}
......
import React from 'react'
import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import { Action, H2, Button, Icon, RadioGroup } from '@pubsweet/ui'
import { states } from 'config'
import { Notification } from '../ui'
import SubmissionCancel from '../SubmissionCancel'
import { QUERY_ACTIVITY_INFO } from './operations'
import { Exit } from './MetaEdit'
import { EditIcon } from './MetaSec'
const FlexP = styled.p`
display: flex;
align-items: center;
& > button {
flex: 0 0 auto;
}
& > span {
flex: 1 1 50%;
margin-left: ${th('gridUnit')};
}
`
const RouteButton = styled(Button)`
align-items: center;
display: inline-flex;
`
const StatusDesc = [
'not yet submitted',
'not yet submitted',
'submission error',
'needs reviewer approval',
'needs submission QA',
'tagging',
'needs XML QA',
'has XML errors',
'needs final review',
'needs citation',
'approved for Europe PMC',
'failed NCBI loading',
'available in Europe PMC',
'being withdrawn',
]
const DeleteButton = ({ deleteMan }) => (
<Action onClick={() => deleteMan()}>Confirm deletion</Action>
)
const RecoverButton = ({ callback, recoverMan }) => (
<RouteButton onClick={() => recoverMan(callback())}>
<Icon color="currentColor" size={2.5}>
refresh-cw
</Icon>
Recover manuscript
</RouteButton>
)
class StatusEdit extends React.Component {
state = {
edit: !this.props.lastStatus,
selected: this.props.lastStatus,
}
render() {
const { manuscript, lastStatus, setStatus, close } = this.props
const { edit, selected } = this.state
const done = states.indexOf('ncbi-ready')
const curr = states.indexOf(manuscript.status)
const options = states.map((s, i) => ({
value: s,
label: `"${s}" – ${StatusDesc[i]}${
s === lastStatus ? ' [most recent]' : ''
}${s === manuscript.status ? ' [current]' : ''}`,
disabled: s === manuscript.status,
}))
return (
<React.Fragment>
<H2>Send/Remove</H2>
{manuscript.deleted ? (
<React.Fragment>
<Notification type="info">
{`Manuscript succesfully deleted. If not already sent, please send a message to the user(s) explaining the deletion.`}
</Notification>
<FlexP>
<SubmissionCancel
callback={close}
manuscriptId={manuscript.id}
refetch={[
{
query: QUERY_ACTIVITY_INFO,
variables: { id: manuscript.id },
},
]}
showComponent={RecoverButton}
/>
</FlexP>
</React.Fragment>
) : (
<React.Fragment>
{manuscript.status === 'submission-error' && (
<FlexP>
<RouteButton
onClick={() => setStatus(lastStatus || 'submitted')}
>
<Icon color="currentColor" size={2.5}>
check-circle
</Icon>
Cancel submission error
</RouteButton>
<span>
Cancel submission error request and send back to
{(lastStatus &&
lastStatus === 'xml-triage' &&
` XML errors state ('xml-triage')`) ||
(lastStatus &&
lastStatus === 'in-review' &&
` Initial review state (in-review)`) ||
` QA state ('submitted')`}
</span>
</FlexP>
)}
<React.Fragment>
<FlexP>
<RouteButton
onClick={() => {
setStatus(selected)
close()
}}
>
<Icon color="currentColor" size={2.5}>
send
</Icon>
Change status
</RouteButton>
<span>
send{selected === lastStatus && ' back'} to <b>{selected}</b>
<Action onClick={() => this.setState({ edit: true })}>
<EditIcon />
</Action>
<em style={{ marginLeft: '8px' }}>
{selected === lastStatus && '(most recent status)'}
</em>
<br />
Please note that no emails will be automatically sent!
</span>
</FlexP>
{edit && (
<RadioGroup
name="Statuses"
onChange={v => this.setState({ selected: v })}
options={options}
value={selected}
/>
)}
<FlexP>
<RouteButton
disabled={curr >= done}
onClick={e =>
e.currentTarget.nextElementSibling.classList.remove(
'hidden',
)
}
>
<Icon color="currentColor" size={2.5}>
trash-2
</Icon>
Delete manuscript
</RouteButton>
<span className="hidden">
{` Are you certain? `}
<SubmissionCancel
manuscriptId={manuscript.id}
refetch={[
{
query: QUERY_ACTIVITY_INFO,
variables: { id: manuscript.id },
},
]}
showComponent={DeleteButton}
/>
</span>
{curr >= done && (
<span>
{` Manuscript has been sent to PMC and must be withdrawn.`}
</span>
)}
</FlexP>
</React.Fragment>
</React.Fragment>
)}
<Exit close={close} />
</React.Fragment>
)
}
}
export default StatusEdit
......@@ -373,8 +373,8 @@ class PubMedSearch extends React.Component {
<FlexP>
<Button
onClick={() => {
adminRemove(inPMC.pmcid)
this.selectResult(inPMC.result, inPMC.journal)
adminRemove(inPMC.pmcid)
}}
>
Remove submission
......
......@@ -4,7 +4,7 @@ import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import { Link, H3, Icon } from '@pubsweet/ui'
import { Loading, LoadingIcon, Table, Notification } from '../ui'
import { COUNT_MANUSCRIPTS } from './operations'
import { COUNT_MANUSCRIPTS, ALERT_MANUSCRIPTS } from './operations'
import DashboardBase from './DashboardBase'
const ListTable = styled(Table)`
......@@ -15,6 +15,12 @@ const ListTable = styled(Table)`
text-align: right;
}
`
const Alert = styled.small`
display: inline-flex;
align-items: center;
color: ${th('colorError')};
margin-left: calc(${th('gridUnit')} * 5);
`
const HelpdeskQueue = {
'needs submission QA': ['submitted'],
'needs XML QA': ['xml-qa'],
......@@ -39,7 +45,32 @@ const Completed = {
deleted: ['deleted'],
}
const QueueTable = ({ title, queue, data }) => {
const AlertQuery = ({ states, interval }) => (
<Query
fetchPolicy="cache-and-network"
query={ALERT_MANUSCRIPTS}
variables={{ states, interval }}
>
{({ data, loading }) => {
if (loading) {
return <LoadingIcon size={1.5} />
}
if (data.checkAge && data.checkAge.alert) {
return (
<Alert>
<Icon color="currentColor" size={2}>
alert-octagon
</Icon>{' '}
Manuscripts older than {interval}
</Alert>
)
}
return null
}}
</Query>
)
const QueueTable = ({ title, queue, data, alerts }) => {
let total = 0
const tableData = Object.keys(queue).map(label => {
const items = data.filter(s => queue[label].includes(s.type))
......@@ -64,11 +95,16 @@ const QueueTable = ({ title, queue, data }) => {
<tr key={row.label}>
<td>{row.count}</td>
<td>
{row.count ? (
<Link to={`/search?status=${row.status}`}>{row.label}</Link>
) : (
row.label
)}
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
{row.count ? (
<Link to={`/search?status=${row.status}`}>{row.label}</Link>
) : (
row.label
)}
{alerts && (
<AlertQuery interval={alerts} states={row.states} />
)}
</span>
</td>
</tr>
))}
......@@ -130,6 +166,7 @@ const Dashboard = ({ currentUser }) => (
title="Submitter/Reviewer queue"
/>
<QueueTable
alerts="5 days"
data={data.countByStatus}
queue={TaggerQueue}
title="Tagger queue"
......
......@@ -24,6 +24,13 @@ export const COUNT_MANUSCRIPTS = gql`
}
}
`
export const ALERT_MANUSCRIPTS = gql`
query CheckAge($states: [String], $interval: String) {
checkAge(states: $states, interval: $interval) {
alert
}
}
`
export const GET_MANUSCRIPT = gql`
query SearchArticleIds($id: String!) {
searchArticleIds(id: $id) {
......
import React from 'react'
import { Field } from 'formik'
import { isEmpty } from 'lodash'
import moment from 'moment'
import { ErrorText, H1, Link, Button, TextField, Checkbox } from '@pubsweet/ui'
import { th } from '@pubsweet/ui-toolkit'
import styled from 'styled-components'
import { notification } from 'config'
import { Page, Notification } from '../ui'
import SignInFooter from '../SignInFooter'
import Holiday from '../Holiday'
const Signup = styled.p`
margin-bottom: 0;
......@@ -23,7 +26,6 @@ const PasswordField = styled.div`
position: relative;
max-width: 500px;
`
const Container = styled.form`
margin: 0 0;
max-width: 350px;
......@@ -60,14 +62,17 @@ class Login extends React.Component {
passwordReset = true,
location,
} = this.props
const { holiday } = notification
return (
<Page>
<H1>Sign in with your Europe PMC plus account</H1>
{notification.show && (
<Notification type={notification.type}>
{notification.message}
</Notification>
)}
{holiday.show && moment().isBefore(holiday.end) && <Holiday />}
{!isEmpty(errors) && <Notification type="error">{errors}</Notification>}
<Notification type="info">
The Europe PMC plus website has recently been upgraded. Please use the
email address associated with your account to log in.
</Notification>
<Container onSubmit={handleSubmit}>
<Field component={EmailInput} name="email" />
<PasswordField>
......
......@@ -77,9 +77,11 @@ const NotifIcon = {
const Notification = ({ children, type, className, ...props }) => (
<Container className={`${className || ''} ${type}`} {...props}>
<Icon color="currentColor" size={2}>
{NotifIcon[type]}
</Icon>
{NotifIcon[type] && (
<Icon color="currentColor" size={2}>
{NotifIcon[type]}
</Icon>
)}
<div>{children}</div>
</Container>
)
......
......@@ -46,6 +46,7 @@ const SelectedLabel = styled.span`
const Selected = styled(Button)`
display: inline-flex;
align-items: center;
text-align: left;
margin: 0 ${th('gridUnit')} ${th('gridUnit')} 0;
${override('ui.SearchSelect.Selected')};
`
......
......@@ -272,6 +272,18 @@ class UploadFileListItem extends React.Component {
setLoading={v => this.setState({ loading: v })}
setNotification={setNotification}
/>