diff --git a/app/components/GrantSearch.jsx b/app/components/GrantSearch.jsx index 631136102b90377baacadecbbce81c4ccb316a13..8ab5658e2bea05730f5a2659e1f0724d1ad08e67 100755 --- a/app/components/GrantSearch.jsx +++ b/app/components/GrantSearch.jsx @@ -188,7 +188,7 @@ class GrantSearch extends React.Component {

Funding

Add all grants that support this manuscript

( + + + gift + + + gift + + + 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.{' '} + + +) + +export default Holiday diff --git a/app/components/activity/MetaEdit.jsx b/app/components/activity/MetaEdit.jsx index 061643341797b9889456cbcad9f550ec933826c7..18385af1295b02c4fcab5fa397c23c47732ba10b 100644 --- a/app/components/activity/MetaEdit.jsx +++ b/app/components/activity/MetaEdit.jsx @@ -1,53 +1,23 @@ 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 }) => ( ) -const DeleteButton = ({ deleteMan }) => ( - deleteMan()}>Confirm deletion -) - -const RecoverButton = ({ callback, recoverMan }) => ( - recoverMan(callback())}> - - refresh-cw - - Recover manuscript - -) - 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 ( -

Send/Remove

- {manuscript.deleted ? ( - - - {`Manuscript succesfully deleted. If not already sent, please send a message to the user(s) explaining the deletion.`} - - - - - - ) : ( - - {manuscript.status === 'submission-error' ? ( - - - props.setStatus(lastStatus || 'submitted') - } - > - - check-circle - - Cancel submission error - - - 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')`} - - - ) : ( - - {lastStatus && ( - - - props.setStatus(lastStatus, close) - } - > - - skip-back - - Send back - - - to previous status of submission: {lastStatus} -
- Note that no emails will be automatically - sent! -
-
- )} - - qa && curr !== xml)} - onClick={() => - props.setStatus( - curr > xml ? 'xml-triage' : 'submitted', - close, - ) - } - > - - send - - Send for routing - - {curr > qa && curr !== xml ? ( - - {` send to most recent routable status:`} - {curr > xml ? ' xml-triage' : ' submitted'} - - ) : ( - - {` The submission is already in a routable state (`} - {manuscript.status} - {`). You may send it backward or forward from the manuscript view.`} - - )} - -
- )} - - = done} - onClick={e => - e.currentTarget.nextElementSibling.classList.remove( - 'hidden', - ) - } - > - - trash-2 - - Delete manuscript - - - {` Are you certain? `} - - - {curr >= done && ( - - {` Manuscript has been sent to PMC and must be withdrawn.`} - - )} - -
- )} - - + ) default: return null diff --git a/app/components/activity/MetaSec.jsx b/app/components/activity/MetaSec.jsx index 469f13bf49804f396ac67be10b810bbb5e434521..ce0ce3a82a8a494e7f27f58b232896721c732711 100644 --- a/app/components/activity/MetaSec.jsx +++ b/app/components/activity/MetaSec.jsx @@ -65,7 +65,7 @@ const DupeButton = styled(Button)` padding: calc(${th('gridUnit')} / 2) ${th('gridUnit')}; font-size: ${th('fontSizeBaseSmall')}; ` -const EditIcon = () => ( +export const EditIcon = () => ( edit-3 diff --git a/app/components/activity/StatusEdit.jsx b/app/components/activity/StatusEdit.jsx new file mode 100644 index 0000000000000000000000000000000000000000..83be126f415b525d0811488943acc6ea76c94595 --- /dev/null +++ b/app/components/activity/StatusEdit.jsx @@ -0,0 +1,195 @@ +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 }) => ( + deleteMan()}>Confirm deletion +) + +const RecoverButton = ({ callback, recoverMan }) => ( + recoverMan(callback())}> + + refresh-cw + + Recover manuscript + +) + +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 ( + +

Send/Remove

+ {manuscript.deleted ? ( + + + {`Manuscript succesfully deleted. If not already sent, please send a message to the user(s) explaining the deletion.`} + + + + + + ) : ( + + {manuscript.status === 'submission-error' && ( + + setStatus(lastStatus || 'submitted')} + > + + check-circle + + Cancel submission error + + + 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')`} + + + )} + + + { + setStatus(selected) + close() + }} + > + + send + + Change status + + + send{selected === lastStatus && ' back'} to {selected} + this.setState({ edit: true })}> + + + + {selected === lastStatus && '(most recent status)'} + +
+ Please note that no emails will be automatically sent! +
+
+ {edit && ( + this.setState({ selected: v })} + options={options} + value={selected} + /> + )} + + = done} + onClick={e => + e.currentTarget.nextElementSibling.classList.remove( + 'hidden', + ) + } + > + + trash-2 + + Delete manuscript + + + {` Are you certain? `} + + + {curr >= done && ( + + {` Manuscript has been sent to PMC and must be withdrawn.`} + + )} + +
+
+ )} + +
+ ) + } +} + +export default StatusEdit diff --git a/app/components/dashboard/AdminDashboard.jsx b/app/components/dashboard/AdminDashboard.jsx index 8b356ced9355e951fc4882b1d56b41bec484c263..9969ef1bc366cd00c362ad2d0a2bf55a02761bd8 100755 --- a/app/components/dashboard/AdminDashboard.jsx +++ b/app/components/dashboard/AdminDashboard.jsx @@ -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 }) => ( + + {({ data, loading }) => { + if (loading) { + return + } + if (data.checkAge && data.checkAge.alert) { + return ( + + + alert-octagon + {' '} + Manuscripts older than {interval} + + ) + } + return null + }} + +) + +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 }) => { {row.count} - {row.count ? ( - {row.label} - ) : ( - row.label - )} + + {row.count ? ( + {row.label} + ) : ( + row.label + )} + {alerts && ( + + )} + ))} @@ -130,6 +166,7 @@ const Dashboard = ({ currentUser }) => ( title="Submitter/Reviewer queue" />

Sign in with your Europe PMC plus account

+ {notification.show && ( + + {notification.message} + + )} + {holiday.show && moment().isBefore(holiday.end) && } {!isEmpty(errors) && {errors}} - - - gift - - - gift - - - Please note that the Helpdesk will be unavailable from Wednesday, 25 - December to Tuesday 2 January. Any calls or e-mails will be saved - and responded to on our return. Happy Holidays from the Europe PMC - team.{' '} - - diff --git a/app/components/ui/molecules/SearchSelect.jsx b/app/components/ui/molecules/SearchSelect.jsx index bd62affacc5f51b5bb77a0683315777af7665de7..ff8ba081d6ed02847439ea7ed03ac3e7304fe10f 100755 --- a/app/components/ui/molecules/SearchSelect.jsx +++ b/app/components/ui/molecules/SearchSelect.jsx @@ -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')}; ` diff --git a/app/epmc-theme/elements/Checkbox.jsx b/app/epmc-theme/elements/Checkbox.jsx index c0f0d92e38838c67128a5a03412c26b4b642b114..abbed7b4a4902deaa6e65906fa9cf0c2c2ac85ca 100755 --- a/app/epmc-theme/elements/Checkbox.jsx +++ b/app/epmc-theme/elements/Checkbox.jsx @@ -1,5 +1,5 @@ import { css } from 'styled-components' -import { th } from '@pubsweet/ui-toolkit' +import { th, lighten } from '@pubsweet/ui-toolkit' export default { Root: css` @@ -13,5 +13,10 @@ export default { `, Input: css` margin-top: calc(${th('gridUnit')} / 2); + + &:disabled + span, + &.disabled + span { + color: ${lighten('colorText', 75)}; + } `, } diff --git a/app/epmc-theme/elements/Radio.jsx b/app/epmc-theme/elements/Radio.jsx index 2762435b603734e8ef117a7542fd92677bfa8d0b..3440ea65af2b588b0ece6d5cfe7239b97a0c3ce0 100755 --- a/app/epmc-theme/elements/Radio.jsx +++ b/app/epmc-theme/elements/Radio.jsx @@ -1,5 +1,5 @@ import { css } from 'styled-components' -import { th } from '@pubsweet/ui-toolkit' +import { th, lighten } from '@pubsweet/ui-toolkit' export default { Root: css` @@ -14,5 +14,10 @@ export default { Input: css` margin-right: ${th('gridUnit')}; height: calc(${th('gridUnit')} * 3); + + &:disabled + span, + &.disabled + span { + color: ${lighten('colorText', 75)}; + } `, } diff --git a/config/default.js b/config/default.js index 383aa26e8c396e5d65addc958f4f1b28c43e79d9..34d0a1c7863f0f79bd1b2e81fb024a31c720eb30 100755 --- a/config/default.js +++ b/config/default.js @@ -212,6 +212,7 @@ module.exports = { 'file', 'pageSize', 'states', + 'notification', ], // do we need this elife variable? elife: { @@ -234,6 +235,17 @@ module.exports = { }, }, states, + notification: { + show: false, + type: 'info', + message: + 'The Europe PMC plus website has recently been upgraded. Please use the email address associated with your account to log in.', + holiday: { + show: true, + start: '2019-12-25T00:00:00Z', + end: '2020-01-02T00:00:00Z', + }, + }, user: { identity: { default: 'local', diff --git a/docker-compose.yml b/docker-compose.yml index 83c54e7bbd563aa2690707f4b2bb1090ada1dc0d..11f3e47fd7df2f2ca51bcd68f11a9a9b3637353c 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,7 @@ services: app: # user: 'node' - image: xpubepmc_app:1.5.2 + image: xpubepmc_app:1.6.0 build: context: . dockerfile: ./Dockerfile diff --git a/k8s/ftp-pv-claim.yaml b/k8s/ftp-pv-claim.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d961f9d0e82013d1c5fca9f33963158cbba8b48e --- /dev/null +++ b/k8s/ftp-pv-claim.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: ftp-pv-claim + labels: + app: ftp-storage-claim +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: gp2 diff --git a/k8s/gp2-storage-class.yaml b/k8s/gp2-storage-class.yaml new file mode 100644 index 0000000000000000000000000000000000000000..48fd4d3267788ed79713b34fac9b66fd6d8fb0f5 --- /dev/null +++ b/k8s/gp2-storage-class.yaml @@ -0,0 +1,11 @@ +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: gp2 + annotations: + storageclass.kubernetes.io/is-default-class: 'true' +provisioner: kubernetes.io/aws-ebs +parameters: + type: gp2 + fsType: ext4 +reclaimPolicy: Retain diff --git a/k8s/xpub-epmc-cluster.yaml b/k8s/xpub-epmc-cluster.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3e4111b0c363e193398439cde0518063f386b35c --- /dev/null +++ b/k8s/xpub-epmc-cluster.yaml @@ -0,0 +1,13 @@ +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig + +metadata: + name: xpub-epmc-cluster + region: eu-west-2 + +nodeGroups: + - name: ng-1 + instanceType: t2.small # 1 vCPU and 2.0 GiB RAM (https://aws.amazon.com/ec2/instance-types/t2/) + desiredCapacity: 3 + ssh: # use existing EC2 key + publicKeyName: literature-service diff --git a/k8s/xpub-epmc-deployment.yaml b/k8s/xpub-epmc-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a02327fd9fb6b6c4cb08586d526a30dd09bc451f --- /dev/null +++ b/k8s/xpub-epmc-deployment.yaml @@ -0,0 +1,176 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: xpub-epmc-deployment + labels: + app: xpub-epmc +spec: + replicas: 1 + selector: + matchLabels: + app: xpub-epmc + template: + metadata: + labels: + app: xpub-epmc + spec: + restartPolicy: Always + volumes: + - name: ftp-storage + persistentVolumeClaim: + claimName: ftp-pv-claim + - name: samba-tmpfs + emptyDir: + medium: Memory + containers: + - name: xpub-epmc + image: 871979166454.dkr.ecr.eu-west-2.amazonaws.com/xpub-epmc:1.0.5 + command: ['/bin/sh'] + args: ['-c', './wrapperScript.sh'] + ports: + - containerPort: 80 + volumeMounts: + - name: ftp-storage + mountPath: /home/xpub/ftpdata + subPath: ftpdata + env: + - name: MINIO_ACCESS_KEY + valueFrom: + secretKeyRef: + name: s3secret + key: S3AccessKey + - name: MINIO_SECRET_KEY + valueFrom: + secretKeyRef: + name: s3secret + key: S3SecretKey + - name: MINIO_ENDPOINT + value: 's3.eu-west-2.amazonaws.com' + - name: MINIO_PORT + value: '443' + - name: MINIO_SECURITY + value: 'true' + - name: MINIO_BUCKET + value: 'xpub-epmc-test' + - name: MINIO_UPLOADS_FOLDER_NAME + value: 'uploads' + - name: MINIO_REGION + value: 'eu-west-2' + - name: PGHOST + valueFrom: + secretKeyRef: + name: pgsecret + key: PGHOST + - name: PGPORT + value: '5432' + - name: PGDATABASE + value: postgres + - name: PGUSER + valueFrom: + secretKeyRef: + name: pgsecret + key: PGUSER + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: pgsecret + key: PGPASSWORD + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: pgsecret + key: PGUSER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: pgsecret + key: PGPASSWORD + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: pgsecret + key: DATABASE_URL + - name: PUBSWEET_URL + valueFrom: + configMapKeyRef: + name: xpub-epmc-config + key: PUBSWEET_URL + - name: PUBSWEET_HOST + valueFrom: + configMapKeyRef: + name: xpub-epmc-config + key: PUBSWEET_HOST + - name: PUBSWEET_EMAIL_URL + valueFrom: + configMapKeyRef: + name: xpub-epmc-config + key: PUBSWEET_EMAIL_URL + - name: PAGE_SIZE + value: '25' + - name: EMAIL_USR + valueFrom: + secretKeyRef: + name: emailsecret + key: User + - name: EMAIL_PWD + valueFrom: + secretKeyRef: + name: emailsecret + key: PWD + - name: APP_SERVER_PORT + value: '80' + - name: NODE_ENV + value: production + - name: ftpd-server + image: stilliard/pure-ftpd:hardened + ports: + - containerPort: 21 + - containerPort: 30000 + - containerPort: 30001 + - containerPort: 30002 + - containerPort: 30003 + - containerPort: 30004 + - containerPort: 30005 + - containerPort: 30006 + - containerPort: 30007 + - containerPort: 30008 + - containerPort: 30009 + env: + - name: PUBLICHOST + valueFrom: + configMapKeyRef: + name: xpub-epmc-config + key: PUBLICHOST + volumeMounts: + - name: ftp-storage + mountPath: /home/ftpusers + subPath: ftpdata + - name: ftp-storage + mountPath: /etc/pure-ftpd + subPath: ftpconfig + - name: ftp-storage + mountPath: /etc/ssl/private + subPath: ftptls + - name: samba + image: dperson/samba + args: + - -s + - Data;/mnt + env: + - name: TZ + value: 'EST5EDT' + volumeMounts: + - name: ftp-storage + mountPath: /mnt:z + subPath: ftpdata + - name: samba-tmpfs + mountPath: /tmp + ports: + - containerPort: 137 + protocol: UDP + - containerPort: 138 + protocol: UDP + - containerPort: 139 + - containerPort: 445 + stdin: true + tty: true diff --git a/k8s/xpub-epmc-ftp-service.yaml b/k8s/xpub-epmc-ftp-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0fe1bc79718ccc81e87d48fc09ebe1d5e999c567 --- /dev/null +++ b/k8s/xpub-epmc-ftp-service.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: Service +metadata: + name: xpub-epmc-ftp-service + labels: + app: xpub-epmc +spec: + type: LoadBalancer + ports: + - name: '21' + port: 21 + targetPort: 21 + - name: '30000' + port: 30000 + targetPort: 30000 + - name: '30001' + port: 30001 + targetPort: 30001 + - name: '30002' + port: 30002 + targetPort: 30002 + - name: '30003' + port: 30003 + targetPort: 30003 + - name: '30004' + port: 30004 + targetPort: 30004 + - name: '30005' + port: 30005 + targetPort: 30005 + - name: '30006' + port: 30006 + targetPort: 30006 + - name: '30007' + port: 30007 + targetPort: 30007 + - name: '30008' + port: 30008 + targetPort: 30008 + - name: '30009' + port: 30009 + targetPort: 30009 + selector: + app: xpub-epmc diff --git a/k8s/xpub-epmc-samba-service.yaml b/k8s/xpub-epmc-samba-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..01f6187baabb45393958f844a248a5b1766d9264 --- /dev/null +++ b/k8s/xpub-epmc-samba-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: xpub-epmc-samba-service + labels: + app: xpub-epmc +spec: + type: LoadBalancer + ports: + - name: '139' + port: 139 + targetPort: 139 + - name: '445' + port: 445 + targetPort: 445 + loadBalancerSourceRanges: + - 193.62.194.244/32 + selector: + app: xpub-epmc diff --git a/k8s/xpub-epmc-service.yaml b/k8s/xpub-epmc-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..03954bb097852ba19739601ca4c04b24e797b010 --- /dev/null +++ b/k8s/xpub-epmc-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: xpub-epmc-service + labels: + app: xpub-epmc +spec: + type: LoadBalancer + ports: + - name: '3000' + port: 80 + targetPort: 80 + protocol: TCP + selector: + app: xpub-epmc diff --git a/k8s/xpub-xsweet-deployment.yaml b/k8s/xpub-xsweet-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ce6e54c350ffb1ffc0c1aa542ff19454369e0ecc --- /dev/null +++ b/k8s/xpub-xsweet-deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: xpub-xsweet-deployment + labels: + app: xpub-xsweet +spec: + replicas: 1 + selector: + matchLabels: + app: xpub-xsweet + template: + metadata: + labels: + app: xpub-xsweet + spec: + containers: + - name: xpub-xsweet + image: pubsweet/job-xsweet:1.3.3 + command: ['/bin/sh'] + args: ['-c', 'node src/xsweet.js'] + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: pgsecret + key: PGUSER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: pgsecret + key: PGPASSWORD + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: pgsecret + key: DATABASE_URL diff --git a/package.json b/package.json index 8bbafddf8f6dbed60f5d185247ce110062d22bc1..9f29958f42b8ee0177e8e315bdb86f6a5726f184 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xpub-epmc", - "version": "1.5.2", + "version": "1.6.0", "private": true, "description": "xpub configured for Europe PMC Plus manuscript submission system", "license": "MIT", diff --git a/server/xpub-model/entities/manuscript/data-access.js b/server/xpub-model/entities/manuscript/data-access.js index f66c4f20fc94be42b751798ba50236a77cab9017..3c79612c2e334783c11acca03c604123398efe48 100644 --- a/server/xpub-model/entities/manuscript/data-access.js +++ b/server/xpub-model/entities/manuscript/data-access.js @@ -361,6 +361,17 @@ class Manuscript extends EpmcBaseModel { return parseInt(count[0].count, 10) } + static async checkAge(statuses, interval) { + const rawString = `"updated" < NOW() - INTERVAL '${interval}'` + const alert = + (await knex('manuscript') + .count('*') + .whereIn('status', statuses) + .whereRaw(rawString) + .whereNull('deleted')) || '0' + return parseInt(alert[0].count, 10) > 0 + } + static async countDeleted(user) { const { isAdmin } = await Manuscript.isAdmin(user) diff --git a/server/xpub-model/entities/manuscript/index.js b/server/xpub-model/entities/manuscript/index.js index 83f6f10a9b798420f6476d39c3f98dc0427aa648..6cc7471fe91e49c6874b82bbcb11c1f5c729864f 100755 --- a/server/xpub-model/entities/manuscript/index.js +++ b/server/xpub-model/entities/manuscript/index.js @@ -187,6 +187,11 @@ const Manuscript = { return counts }, + checkAge: async (states, interval) => { + const older = await ManuscriptAccess.checkAge(states, interval) + return { alert: older } + }, + search: async (query, page, pageSize, user) => { const manuscripts = await ManuscriptAccess.searchByTitleOrUser( query, diff --git a/server/xpub-server/entities/email/resolvers.js b/server/xpub-server/entities/email/resolvers.js index c36af9ee5f7017910b508a480e8a7395ad8c3d17..5e2d0ffff49fab3696aa32ad5a21d72d7f4b3b66 100644 --- a/server/xpub-server/entities/email/resolvers.js +++ b/server/xpub-server/entities/email/resolvers.js @@ -116,14 +116,14 @@ const resolvers = { // Update password reset token const identity = user.identities.reduce((id, identity) => { - if (identity.email === email) { + if (identity.email.toLowerCase() === email.toLowerCase()) { return identity } return id }, undefined) if (!identity) { - throw new Error(`The user is not associated with email ${email}`) + throw new Error(`No user is associated with email ${email}`) } let numUpdated = 0 diff --git a/server/xpub-server/entities/manuscript/resolvers.js b/server/xpub-server/entities/manuscript/resolvers.js index ec14ab1c6c29286dfc4797345350c8011b400324..18c38c2818a736784ed245dc85f4f5931c89252c 100644 --- a/server/xpub-server/entities/manuscript/resolvers.js +++ b/server/xpub-server/entities/manuscript/resolvers.js @@ -31,6 +31,12 @@ const resolvers = { } return ManuscriptManager.countByStatus(user) }, + async checkAge(_, { states, interval }, { user }) { + if (!user) { + throw new Error('You are not authenticated!') + } + return ManuscriptManager.checkAge(states, interval) + }, async adminManuscripts(_, { external }, { user }) { if (!user) { throw new Error('You are not authenticated!') diff --git a/server/xpub-server/entities/manuscript/typeDefs.graphqls b/server/xpub-server/entities/manuscript/typeDefs.graphqls index 895b61a3bc5e74549a236c3ef8f5370fe06d3fe0..8c315999a3347095a218711427f47857fd7de740 100644 --- a/server/xpub-server/entities/manuscript/typeDefs.graphqls +++ b/server/xpub-server/entities/manuscript/typeDefs.graphqls @@ -3,6 +3,10 @@ type Count { count: Int } +type Older { + alert: Boolean +} + type Error { message: String } @@ -28,6 +32,7 @@ extend type Query { manuscripts: [Manuscript]! adminManuscripts(external: Boolean): ManuscriptSearchResult! countByStatus: [Count]! + checkAge(states: [String], interval: String): Older! checkDuplicates(id: ID!, articleIds: [String], title: String!): ManuscriptSearchResult! searchArticleIds(id: String!): ManuscriptResult! findByStatus(query: String!, page: Int, pageSize: Int): ManuscriptSearchResult!