From 1627fb146ac0c5858df4332ef473e6aa23803b34 Mon Sep 17 00:00:00 2001 From: ahamelers <audrey@ahamelers.com> Date: Wed, 23 Jan 2019 17:33:59 +0000 Subject: [PATCH] citations, normal journal added correctly now, WIP: still working on unmatched citation --- .../citation-search/PubMedSearch.jsx | 198 ++++--- .../citation-search/UnmatchedCitation.jsx | 2 +- app/components/citation-search/operations.js | 45 ++ .../submission-wizard/CreatePage.jsx | 5 +- .../submission-wizard/CreateSetupPage.jsx | 2 +- .../submission-wizard/SubmitPage.jsx | 11 +- .../submission-wizard/operations.js | 8 - .../entities/journal/data-access.js | 13 +- server/xpub-model/entities/journal/index.js | 1 + .../xpub-model/entities/manuscript/index.js | 8 +- server/xpub-model/index.js | 2 + .../xpub-server/entities/journal/resolvers.js | 17 + .../entities/journal/typeDefs.graphqls | 3 + .../entities/manuscript/typeDefs.graphqls | 2 +- .../entities/note/resolvers.test.js | 520 ------------------ server/xpub-server/index.js | 1 + 16 files changed, 199 insertions(+), 639 deletions(-) create mode 100644 app/components/citation-search/operations.js create mode 100644 server/xpub-server/entities/journal/resolvers.js create mode 100644 server/xpub-server/entities/journal/typeDefs.graphqls delete mode 100644 server/xpub-server/entities/note/resolvers.test.js diff --git a/app/components/citation-search/PubMedSearch.jsx b/app/components/citation-search/PubMedSearch.jsx index 7c40bf292..13ebd3c1e 100755 --- a/app/components/citation-search/PubMedSearch.jsx +++ b/app/components/citation-search/PubMedSearch.jsx @@ -1,6 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom' -import { withRouter } from 'react-router-dom' +import { ApolloConsumer } from 'react-apollo' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' import { Action, Button, Icon, H2, Link } from '@pubsweet/ui' @@ -18,6 +18,7 @@ import { SearchForm, ZebraList, } from '../ui' +import { JOURNAL_INFO } from './operations' import PubMedSearchResult from './PubMedSearchResult' import UnmatchedCitation from './UnmatchedCitation' @@ -54,9 +55,9 @@ class PubMedSearch extends React.Component { inEPMC: false, unmatched: this.props.metadata && - (!this.props.metadata.articleId || - this.props.metadata.articleId.length === 0), - // pubProvided: null, + (!this.props.metadata.articleIds || + this.props.metadata.articleIds.length === 0), + pubProvided: false, } this.onSearch = this.onSearch.bind(this) this.onLoad = this.onLoad.bind(this) @@ -117,7 +118,7 @@ class PubMedSearch extends React.Component { } this.setState({ loading: false }) } - async onSelected(result) { + async onSelected(result, journal) { const inPMC = result.articleids.find(id => id.idtype === 'pmc') if (inPMC) { const obj = { @@ -134,43 +135,49 @@ class PubMedSearch extends React.Component { obj.inEPMC = true } this.setState(obj) + } else if (journal.meta.pmcStatus) { + this.setState({ pubProvided: journal.journalTitle }) } else { - // TODO: Add test for journals that participate in (are indexed by) PMC. Processing does not need to continue for these. - - /* const journalUrl = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=nlmcatalog&id=${result.nlmuniqueid}&retmode=xml` - const response = await fetch(journalUrl) - const xml = await response.text() - const indexedby = xml.match(/<IndexingSourceName(.*)<\/IndexingSourceName>/g) - console.log(indexedby) */ const date = new Date(result.sortpubdate) const citationData = { - title: result.title, - articleIds: [ - { - pubIdType: 'pmid', - id: result.uid, - }, - ], - publicationDates: [ - { - type: 'ppub', - date, - }, - ], - nlmId: result.nlmuniqueid, + journalId: journal.id, + meta: { + title: result.title, + articleIds: [ + { + pubIdType: 'pmid', + id: result.uid, + }, + ], + publicationDates: [ + { + type: 'ppub', + date, + }, + ], + }, } this.props.citationData(citationData) } } render() { - const { results, hitcount, loading, query, inPMC, pmcid } = this.state - const { metadata } = this.props - const { title, journalMeta, customMeta, notes } = metadata || {} + const { + results, + hitcount, + loading, + query, + inPMC, + inEPMC, + pubProvided, + pmcid, + } = this.state + const { manuscript } = this.props + const { meta, notes, journal } = manuscript || {} const note = notes ? notes.find(n => n.notesType === 'user citation') : '' - let journal = journalMeta - if (!journal && customMeta && customMeta.unmatchedJournal) { - journal = { - title: customMeta.unmatchedJournal, + let journalInfo = journal + if (!journalInfo && meta && meta.unmatchedJournal) { + journalInfo = { + journalTitle: meta.unmatchedJournal, } } if (this.state.unmatched) { @@ -181,9 +188,9 @@ class PubMedSearch extends React.Component { <CloseIcon onClick={() => this.setState({ unmatched: false })} /> </Close> <UnmatchedCitation - journal={journal} + journal={journalInfo} note={note ? note.content : ''} - title={title} + title={meta.title} unmatchedInfo={this.handleUnmatchedInfo} /> </Page> @@ -193,31 +200,43 @@ class PubMedSearch extends React.Component { } return ( <SearchArea> - {inPMC ? ( - <div> + {inPMC || pubProvided ? ( + <React.Fragment> <H2>No further action is required for this manuscript</H2> - <p> - The full text of the article ' - <HTMLString string={inPMC} /> - ' - {` has already been sent to Europe PMC. Please use `} - <B>{pmcid}</B> - {` for grant reporting purposes.`} - </p> - {this.state.inEPMC && ( - <Links> - <A href={`https://europepmc.org/articles/${pmcid}`}> - <Icon color="currentColor">arrow-right-circle</Icon> - View this article on Europe PMC - </A> - <br /> - <A - href={`https://europepmc.org/orcid/import?3-1.ILinkListener-startwizard?query=${pmcid}`} - > - <Icon color="currentColor">arrow-right-circle</Icon> - Claim this article to your ORCID - </A> - </Links> + {inPMC ? ( + <React.Fragment> + <p> + The full text of the article ' + <HTMLString string={inPMC} /> + ' + {` has already been sent to Europe PMC. Please use `} + <B>{pmcid}</B> + {` for grant reporting purposes.`} + </p> + {inEPMC && ( + <Links> + <A href={`https://europepmc.org/articles/${pmcid}`}> + <Icon color="currentColor">arrow-right-circle</Icon> + View this article on Europe PMC + </A> + {/* TODO: ORCID claiming + <br /> + <A + href={`https://europepmc.org/orcid/import?3-1.ILinkListener-startwizard?query=${pmcid}`} + > + <Icon color="currentColor">arrow-right-circle</Icon> + Claim this article to your ORCID + </A> */} + </Links> + )} + </React.Fragment> + ) : ( + <p> + Your journal, {pubProvided.journalTitle}, participates in PMC. + Your article will be provided to Europe PMC, through PMC, by the + publisher. Please contact the publisher regarding any delays in + this process. + </p> )} <Buttons> <Link to="/"> @@ -229,15 +248,16 @@ class PubMedSearch extends React.Component { inPMC: null, pmcid: null, inEPMC: false, + pubProvided: null, }) } > Back to Search </Button> </Buttons> - </div> + </React.Fragment> ) : ( - <div> + <React.Fragment> <SearchForm buttonLabel="Search" disabled={!this.state.enabled} @@ -266,39 +286,49 @@ class PubMedSearch extends React.Component { </Notice> )} {results.length > 0 && ( - <ZebraList style={{ textAlign: 'center' }}> - {results.map( - result => - result.fulljournalname && ( - <PubMedSearchResult - key={result.uid} - onClick={() => this.onSelected(result)} - result={result} - /> - ), - )} - {loading && ( - <Loading> - <LoadingIcon /> - </Loading> - )} - {results.length < hitcount && !loading && ( - <LoadMore onClick={this.onLoad} secondary> - Load More Results - </LoadMore> + <ApolloConsumer> + {client => ( + <ZebraList style={{ textAlign: 'center' }}> + {results.map( + result => + result.fulljournalname && ( + <PubMedSearchResult + key={result.uid} + onClick={async () => { + const { data } = await client.query({ + query: JOURNAL_INFO, + variables: { nlmId: result.nlmuniqueid }, + }) + this.onSelected(result, data.selectWithNLM) + }} + result={result} + /> + ), + )} + {loading && ( + <Loading> + <LoadingIcon /> + </Loading> + )} + {results.length < hitcount && !loading && ( + <LoadMore onClick={this.onLoad} secondary> + Load More Results + </LoadMore> + )} + </ZebraList> )} - </ZebraList> + </ApolloConsumer> )} {results.length === 0 && loading && ( <Loading> <LoadingIcon /> </Loading> )} - </div> + </React.Fragment> )} </SearchArea> ) } } -export default withRouter(PubMedSearch) +export default PubMedSearch diff --git a/app/components/citation-search/UnmatchedCitation.jsx b/app/components/citation-search/UnmatchedCitation.jsx index 72de2aacb..cfa26696b 100755 --- a/app/components/citation-search/UnmatchedCitation.jsx +++ b/app/components/citation-search/UnmatchedCitation.jsx @@ -17,7 +17,7 @@ export default class UnmatchedCitation extends React.Component { this.state = { loading: false, title: this.props.title, - query: this.props.journal ? this.props.journal.title : '', + query: this.props.journal ? this.props.journal.journalTitle : '', journal: this.props.journal, note: this.props.note, enabled: this.props.title && this.props.journal, diff --git a/app/components/citation-search/operations.js b/app/components/citation-search/operations.js new file mode 100644 index 000000000..c11190531 --- /dev/null +++ b/app/components/citation-search/operations.js @@ -0,0 +1,45 @@ +import gql from 'graphql-tag' + +export const JOURNAL_INFO = gql` + query($nlmId: String!) { + selectWithNLM(nlmId: $nlmId) { + id + journalTitle + meta { + nlmta + pmcStatus + pubmedStatus + } + } + } +` + +export const CREATE_NOTE = gql` + mutation CreateNote($data: NewNoteInput!) { + createNote(data: $data) { + id + meta { + notes { + id + notesType + content + } + } + } + } +` + +export const UPDATE_NOTE = gql` + mutation UpdateNote($data: NoteInput!) { + updateNote(data: $data) { + id + meta { + notes { + id + notesType + content + } + } + } + } +` diff --git a/app/components/submission-wizard/CreatePage.jsx b/app/components/submission-wizard/CreatePage.jsx index 23e0b231e..c0d426166 100755 --- a/app/components/submission-wizard/CreatePage.jsx +++ b/app/components/submission-wizard/CreatePage.jsx @@ -147,7 +147,8 @@ class Created extends React.Component { render() { const currentUser = this.context const { currentStep, status, error, checked } = this.state - const { id: mId, meta, journal, files: allfiles } = this.props.manuscript + const { manuscript } = this.props + const { id: mId, meta, journal, files: allfiles } = manuscript const { notes } = meta const files = allfiles ? allfiles.filter( @@ -213,7 +214,7 @@ class Created extends React.Component { <H2>Citation</H2> <PubMedSearch citationData={changeCitation} - metadata={meta} + manuscript={manuscript} /> </React.Fragment> ) : ( diff --git a/app/components/submission-wizard/CreateSetupPage.jsx b/app/components/submission-wizard/CreateSetupPage.jsx index 073a4f16e..4c41f3871 100755 --- a/app/components/submission-wizard/CreateSetupPage.jsx +++ b/app/components/submission-wizard/CreateSetupPage.jsx @@ -17,7 +17,7 @@ const manuscriptSetup = async ( const created = await createManuscript() const { id } = created.data.createManuscript await updateManuscript({ - variables: { data: { id, meta: citationData } }, + variables: { data: { id, ...citationData } }, }) const route = `/submission/${id}/create` history.push(route) diff --git a/app/components/submission-wizard/SubmitPage.jsx b/app/components/submission-wizard/SubmitPage.jsx index c0fa9686f..df41cf01a 100755 --- a/app/components/submission-wizard/SubmitPage.jsx +++ b/app/components/submission-wizard/SubmitPage.jsx @@ -116,13 +116,8 @@ class Submit extends React.Component { `${name.title ? `${name.title} ` : ''}${name.givenNames} ${name.surname}` render() { const currentUser = this.context - const { - id: mId, - meta, - files: allfiles, - status, - teams, - } = this.props.manuscript + const { manuscript } = this.props + const { id: mId, meta, files: allfiles, status, teams } = manuscript if (teams && allfiles) { const { editing, references, regexes } = this.state const fundingGroup = meta.fundingGroup ? meta.fundingGroup : [] @@ -170,7 +165,7 @@ class Submit extends React.Component { <H3>Citation</H3> <PubMedSearch citationData={this.createNewManuscriptVersion} - metadata={meta} + manuscript={manuscript} /> </div> ), diff --git a/app/components/submission-wizard/operations.js b/app/components/submission-wizard/operations.js index c52a14982..ec6b7347c 100644 --- a/app/components/submission-wizard/operations.js +++ b/app/components/submission-wizard/operations.js @@ -150,14 +150,6 @@ export const UPDATE_NOTE = gql` } ` -export const DELETE_NOTE = gql` - mutation DeleteNote($id: ID!) { - deleteNote(id: $id) { - id - } - } -` - export const SUBMIT_MANUSCRIPT = gql` mutation SubmitManuscript($data: ManuscriptInput!) { submitManuscript(data: $data) { diff --git a/server/xpub-model/entities/journal/data-access.js b/server/xpub-model/entities/journal/data-access.js index 6ef633f05..dc623e0b6 100644 --- a/server/xpub-model/entities/journal/data-access.js +++ b/server/xpub-model/entities/journal/data-access.js @@ -60,18 +60,17 @@ class Journal extends EpmcBaseModel { return rowToEntity(rows[0]) } - static async getIdByNlmId(nlmId) { - const row = await runQuery( + static async selectByNlmId(nlmId) { + const rows = await runQuery( buildQuery - .select('journal.id') + .select('journal.*') .from('journal') - .where('meta,nlmuniqueid', nlmId) - .first(), + .where('meta,nlmuniqueid', nlmId), ) - if (!row) { + if (!rows) { throw new Error('journal not found') } - return row.id + return rowToEntity(rows[0]) } static async selectAll() { diff --git a/server/xpub-model/entities/journal/index.js b/server/xpub-model/entities/journal/index.js index 2c47dfdac..832a196d6 100755 --- a/server/xpub-model/entities/journal/index.js +++ b/server/xpub-model/entities/journal/index.js @@ -23,6 +23,7 @@ const JournalManager = { return { ...journal, id } }, + selectWithNLM: async nlmId => Journal.selectByNlmId(nlmId), } module.exports = JournalManager diff --git a/server/xpub-model/entities/manuscript/index.js b/server/xpub-model/entities/manuscript/index.js index dfa41dbef..ca6dc2b31 100755 --- a/server/xpub-model/entities/manuscript/index.js +++ b/server/xpub-model/entities/manuscript/index.js @@ -9,7 +9,6 @@ const FileAccess = require('../file/data-access') const NoteAccess = require('../note/data-access') const ReviewAccess = require('../review/data-access') const Team = require('../team/data-access') -const Journal = require('../journal/data-access') const { dManuscriptUpdate, gManuscript } = require('./helpers/transform') const mergeObjects = (...inputs) => @@ -139,12 +138,7 @@ const Manuscript = { if (!originalMan) { throw new Error('Manuscript not found') } - const newInput = input - if (input.meta.nlmId) { - input.journalId = await Journal.getIdByNlmId(input.meta.nlmId) - delete newInput.meta.nlmId - } - const manuscriptUpdate = dManuscriptUpdate(newInput, userId) + const manuscriptUpdate = dManuscriptUpdate(input, userId) lodash.assign(originalMan, manuscriptUpdate) await originalMan.save() const updatedMan = await ManuscriptAccess.selectById(input.id, true) diff --git a/server/xpub-model/index.js b/server/xpub-model/index.js index 5b8e19742..f31cd1750 100644 --- a/server/xpub-model/index.js +++ b/server/xpub-model/index.js @@ -5,6 +5,7 @@ const NoteManager = require('./entities/note') const RoleManager = require('./entities/role') const TeamManager = require('./entities/team') const UserManager = require('./entities/user') +const JournalManager = require('./entities/journal') const OrganizationManager = require('./entities/organization') module.exports = { @@ -15,5 +16,6 @@ module.exports = { RoleManager, TeamManager, UserManager, + JournalManager, OrganizationManager, } diff --git a/server/xpub-server/entities/journal/resolvers.js b/server/xpub-server/entities/journal/resolvers.js new file mode 100644 index 000000000..415fcf7c4 --- /dev/null +++ b/server/xpub-server/entities/journal/resolvers.js @@ -0,0 +1,17 @@ +const rfr = require('rfr') + +const { JournalManager } = rfr('server/xpub-model') + +const resolvers = { + Query: { + async selectWithNLM(_, { nlmId }, { user }) { + if (!user) { + throw new Error('You are not authenticated!') + } + + return JournalManager.selectWithNLM(nlmId) + }, + }, +} + +module.exports = resolvers diff --git a/server/xpub-server/entities/journal/typeDefs.graphqls b/server/xpub-server/entities/journal/typeDefs.graphqls new file mode 100644 index 000000000..4b5b4c416 --- /dev/null +++ b/server/xpub-server/entities/journal/typeDefs.graphqls @@ -0,0 +1,3 @@ +extend type Query { + selectWithNLM(nlmId: String!): Journal! +} \ No newline at end of file diff --git a/server/xpub-server/entities/manuscript/typeDefs.graphqls b/server/xpub-server/entities/manuscript/typeDefs.graphqls index 54d9d236d..ef2a633ad 100644 --- a/server/xpub-server/entities/manuscript/typeDefs.graphqls +++ b/server/xpub-server/entities/manuscript/typeDefs.graphqls @@ -29,6 +29,7 @@ extend type Mutation { input ManuscriptInput { id: ID! status: String + journalId: ID meta: ManuscriptMetaInput } @@ -39,7 +40,6 @@ input ManuscriptMetaInput { fundingGroup: [FundingGroupInput] releaseDelay: String unmatchedJournal: String - nlmId: String } input ArticleIdInput { diff --git a/server/xpub-server/entities/note/resolvers.test.js b/server/xpub-server/entities/note/resolvers.test.js deleted file mode 100644 index 3e3d26d07..000000000 --- a/server/xpub-server/entities/note/resolvers.test.js +++ /dev/null @@ -1,520 +0,0 @@ -/* jest.mock('@pubsweet/logger') -jest.mock('@elifesciences/xpub-meca-export', () => ({ - mecaExport: jest.fn(() => Promise.resolve()), -})) - -const lodash = require('lodash') -const config = require('config') -const fs = require('fs-extra') -const stream = require('stream') -const logger = require('@pubsweet/logger') -const { createTables } = require('@pubsweet/db-manager') -const mailer = require('@pubsweet/component-send-email') -// const { mecaExport } = require('@elifesciences/xpub-meca-export') -const startS3rver = require('../../test/mock-s3-server') -const { - UserManager: User, - FileManager, - ManuscriptManager: Manuscript, -} = require('@elifesciences/xpub-model') -const { Mutation, Query } = require('./resolvers') -const { - userData, - badUserData, - expectedManuscript, - manuscriptInput, -} = require('./resolvers.test.data') - -// const replaySetup = require('../../../../test/helpers/replay-setup') - -describe('Manuscripts', () => { - const profileId = userData.identities[0].identifier - const badProfileId = badUserData.identities[0].identifier - let userId - - beforeEach(async () => { - // replaySetup('success') - await Promise.all([ - fs.remove(config.get('pubsweet-server.uploads')), - createTables(true), - ]) - const [user] = await Promise.all([ - User.save(userData), - User.save(badUserData), - ]) - userId = user.id - mailer.clearMails() - }) - - describe('manuscript', () => { - it('Gets form data', async () => { - const manuscriptData = { - createdBy: userId, - meta: { title: 'title' }, - status: 'INITIAL', - } - const { id } = await Manuscript.save(manuscriptData) - - const manuscript = await Query.manuscript({}, { id }, { user: profileId }) - expect(manuscript).toMatchObject(manuscriptData) - }) - }) - - describe('createManuscript', () => { - it('fails if no authenticated user', async () => { - await expect(Mutation.createManuscript({}, {}, {})).rejects.toThrow( - 'Not logged in', - ) - }) - - it('adds new manuscript to the db for current user with status INITIAL', async () => { - const manuscript = await Mutation.createManuscript( - {}, - {}, - { user: profileId }, - ) - - const manuscripts = await Manuscript.findByStatus('INITIAL', userId) - expect(manuscripts.length).toBeGreaterThan(0) - expect(manuscripts[0].id).toBe(manuscript.id) - }) - }) - - describe('updateManuscript', () => { - it("fails if manuscript doesn't belong to user", async () => { - const blankManuscript = Manuscript.new({ createdBy: userId }) - const manuscript = await Manuscript.save(blankManuscript) - await expect( - Mutation.updateManuscript( - {}, - { data: { id: manuscript.id } }, - { user: badProfileId }, - ), - ).rejects.toThrow('Manuscript not found') - }) - - it('fails if manuscript has already been submitted', async () => { - const blankManuscript = Manuscript.new({ - createdBy: userId, - status: Manuscript.statuses.MECA_EXPORT_PENDING, - }) - const manuscript = await Manuscript.save(blankManuscript) - await expect( - Mutation.updateManuscript( - {}, - { data: { id: manuscript.id } }, - { user: profileId }, - ), - ).rejects.toThrow( - 'Cannot update manuscript with status of MECA_EXPORT_PENDING', - ) - }) - - it('updates the current submission for user with data and emails', async () => { - const NUM_EMAILS = 1 - const blankManuscript = Manuscript.new({ createdBy: userId }) - const manuscript = await Manuscript.save(blankManuscript) - - await Mutation.updateManuscript( - {}, - { data: { id: manuscript.id, ...manuscriptInput } }, - { user: profileId }, - ) - await waitforEmails(NUM_EMAILS) - - const allEmails = mailer.getMails() - expect(allEmails).toHaveLength(NUM_EMAILS) - - // Check the dashboard email - expect(allEmails[0]).toMatchObject({ - to: 'mymail@mail.com', - subject: 'Libero Submission System: New Submission', - }) - - const actualManuscript = await Manuscript.find(manuscript.id, userId) - expect(actualManuscript).toMatchObject(expectedManuscript) - }) - }) - - describe('submitManuscript', () => { - let id, initialManuscript - - beforeAll(() => - jest - .spyOn(FileManager, 'getContent') - .mockImplementation(() => 'A real PDF')) - - afterAll(() => FileManager.getContent.mockRestore()) - - beforeEach(async () => { - jest.clearAllMocks() - - initialManuscript = Manuscript.new({ createdBy: userId }) - initialManuscript.files = [ - { - url: 'fake-path.pdf', - filename: 'FakeManuscript.pdf', - type: 'MANUSCRIPT_SOURCE', - }, - ] - - const manuscript = await Manuscript.save(initialManuscript) - id = manuscript.id - }) - - it('stores data with new status', async () => { - const returnedManuscript = await Mutation.submitManuscript( - {}, - { data: { ...manuscriptInput, id } }, - { user: profileId }, - ) - - expect(returnedManuscript.status).toBe( - Manuscript.statuses.MECA_EXPORT_PENDING, - ) - - const storedManuscript = await Manuscript.find(id, userId) - - expect(storedManuscript).toMatchObject({ - ...expectedManuscript, - status: expect.stringMatching(/^MECA_EXPORT_(SUCCEEDED|PENDING)/), - }) - }) - - it('calls meca export with correct arguments', async () => { - const ip = '1.2.3.4' - await Mutation.submitManuscript( - {}, - { data: { ...manuscriptInput, id } }, - { user: profileId, ip }, - ) - - expect(mecaExport).toHaveBeenCalled() - const [ - actualManuscript, - actualContent, - actualIp, - ] = mecaExport.mock.calls[0] - expect(actualManuscript.id).toBe(id) - expect(actualContent).toBe('A real PDF') - expect(actualIp).toBe(ip) - }) - - it('removes blank optional reviewer rows', async () => { - const manuscript = lodash.cloneDeep(manuscriptInput) - manuscript.id = id - manuscript.suggestedReviewers = [ - { name: 'Reviewer 1', email: 'reviewer1@mail.com' }, - { name: 'Reviewer 2', email: 'reviewer2@mail.com' }, - { name: 'Reviewer 3', email: 'reviewer3@mail.com' }, - { name: '', email: '' }, - { name: 'Reviewer 4', email: 'reviewer4@mail.com' }, - { name: '', email: '' }, - ] - await Mutation.submitManuscript( - {}, - { data: manuscript }, - { user: profileId }, - ) - - const storedManuscript = await Manuscript.find(id, userId) - const team = storedManuscript.teams.find( - t => t.role === 'suggestedReviewer', - ) - expect(team.teamMembers.map(member => member.meta)).toEqual([ - { name: 'Reviewer 1', email: 'reviewer1@mail.com' }, - { name: 'Reviewer 2', email: 'reviewer2@mail.com' }, - { name: 'Reviewer 3', email: 'reviewer3@mail.com' }, - { name: 'Reviewer 4', email: 'reviewer4@mail.com' }, - ]) - }) - - it("fails if manuscript doesn't belong to user", async () => { - const blankManuscript = Manuscript.new({ createdBy: userId }) - const manuscript = await Manuscript.save(blankManuscript) - await expect( - Mutation.submitManuscript( - {}, - { data: { id: manuscript.id } }, - { user: badProfileId }, - ), - ).rejects.toThrow('Manuscript not found') - }) - - it('fails if manuscript has already been submitted', async () => { - const blankManuscript = Manuscript.new({ - createdBy: userId, - status: Manuscript.statuses.MECA_EXPORT_PENDING, - }) - const manuscript = await Manuscript.save(blankManuscript) - await expect( - Mutation.submitManuscript( - {}, - { data: { id: manuscript.id } }, - { user: profileId }, - ), - ).rejects.toThrow( - 'Cannot submit manuscript with status of MECA_EXPORT_PENDING', - ) - }) - - // TODO more tests needed here - it('fails when manuscript has incomplete data', async () => { - const badManuscript = { - id, - title: 'Some Title', - } - await expect( - Mutation.submitManuscript( - {}, - { data: badManuscript }, - { user: profileId }, - ), - ).rejects.toThrow() - }) - - it('fails when manuscript has bad data types', async () => { - const badManuscript = lodash.cloneDeep(manuscriptInput) - lodash.merge(badManuscript, { - id, - title: 100, - manuscriptType: {}, - }) - await expect( - Mutation.submitManuscript( - {}, - { data: badManuscript }, - { user: profileId }, - ), - ).rejects.toThrow( - '"title" is not allowed. "manuscriptType" is not allowed', - ) - }) - - it('sends email and updates status when export fails', async () => { - const NUM_EMAILS = 1 - jest.spyOn(logger, 'error').mockImplementationOnce(() => {}) - mecaExport.mockImplementationOnce(() => - Promise.reject(new Error('Broked')), - ) - - const manuscript = lodash.cloneDeep(manuscriptInput) - manuscript.id = id - await Mutation.submitManuscript( - {}, - { data: manuscript }, - { user: profileId }, - ) - - await waitforEmails(NUM_EMAILS) - - const allEmails = mailer.getMails() - expect(allEmails).toHaveLength(NUM_EMAILS) - - // Check the MECA fail email - expect(allEmails[0]).toMatchObject({ - to: 'test@example.com', - subject: 'MECA export failed', - }) - - expect(logger.error).toHaveBeenCalled() - const updatedManuscript = await Manuscript.find(manuscript.id, userId) - expect(updatedManuscript.status).toBe( - Manuscript.statuses.MECA_EXPORT_FAILED, - ) - }) - }) - - describe('uploadManuscript', () => { - let s3Server - - beforeEach(async () => { - const server = await startS3rver({ - ...config.get('aws.credentials'), - ...config.get('aws.s3'), - }) - s3Server = server.instance - }) - - afterEach(done => { - s3Server.close(done) - }) - - it("fails if manuscript doesn't belong to user", async () => { - const blankManuscript = Manuscript.new() - blankManuscript.createdBy = userId - const manuscript = await Manuscript.save(blankManuscript) - - await expect( - Mutation.uploadManuscript( - {}, - { id: manuscript.id }, - { user: badProfileId }, - ), - ).rejects.toThrow('Manuscript not found') - }) - - it('saves manuscript to S3', async () => { - const blankManuscript = Manuscript.new() - blankManuscript.createdBy = userId - const { id } = await Manuscript.save(blankManuscript) - const file = { - filename: 'manuscript.pdf', - stream: fs.createReadStream( - `${__dirname}/../../../../test/fixtures/dummy-manuscript-2.pdf`, - ), - mimetype: 'application/pdf', - } - const manuscript = await Mutation.uploadManuscript( - {}, - { id, file, fileSize: 73947 }, - { user: profileId }, - ) - - const pdfBinary = await FileManager.getContent( - Manuscript.getSource(manuscript), - ) - expect(pdfBinary.toString().substr(0, 6)).toEqual('%PDF-1') - }) - - it('fails if S3 upload fails', async () => { - jest - .spyOn(FileManager, 'putContent') - .mockImplementationOnce(() => - Promise.reject(new Error('Failed to persist file')), - ) - const blankManuscript = Manuscript.new({ createdBy: userId }) - const { id } = await Manuscript.save(blankManuscript) - const file = { - filename: 'manuscript.pdf', - stream: fs.createReadStream( - `${__dirname}/../../../../test/fixtures/dummy-manuscript-2.pdf`, - ), - mimetype: 'application/pdf', - } - await expect( - Mutation.uploadManuscript( - {}, - { id, file, fileSize: 73947 }, - { user: profileId }, - ), - ).rejects.toThrow('Failed to persist file') - - const manuscript = await Manuscript.find(id, userId) - expect(manuscript.files).toHaveLength(0) - }) - - it('sets empty title if ScienceBeam fails', async () => { - jest.spyOn(logger, 'warn').mockImplementationOnce(() => {}) - // replaySetup('error') - const blankManuscript = Manuscript.new() - blankManuscript.createdBy = userId - const { id } = await Manuscript.save(blankManuscript) - const file = { - filename: 'manuscript.pdf', - stream: fs.createReadStream( - `${__dirname}/../../../../test/fixtures/dummy-manuscript-2.pdf`, - ), - mimetype: 'application/pdf', - } - const manuscript = await Mutation.uploadManuscript( - {}, - { id, file, fileSize: 73947 }, - { user: profileId }, - ) - expect(manuscript).toMatchObject({ - id, - meta: { title: '' }, - files: [{ filename: 'manuscript.pdf' }], - }) - expect(logger.warn).toHaveBeenCalled() - }) - - it('extracts title from PDF', async () => { - const blankManuscript = Manuscript.new() - blankManuscript.createdBy = userId - const { id } = await Manuscript.save(blankManuscript) - const file = { - filename: 'manuscript.pdf', - stream: fs.createReadStream( - `${__dirname}/../../../../test/fixtures/dummy-manuscript-2.pdf`, - ), - mimetype: 'application/pdf', - } - const manuscript = await Mutation.uploadManuscript( - {}, - { id, file, fileSize: 73947 }, - { user: profileId }, - ) - expect(manuscript).toMatchObject({ - id, - meta: { - title: - 'The Relationship Between Lamport Clocks and Interrupts Using Obi', - }, - files: [{ filename: 'manuscript.pdf' }], - }) - }) - - it(`fails if manuscript size is bigger than ${config.get( - 'fileUpload.maxSizeMB', - )}MB`, async () => { - const blankManuscript = Manuscript.new() - blankManuscript.createdBy = userId - const { id } = await Manuscript.save(blankManuscript) - - const maxFileSize = config.get('fileUpload.maxSizeMB') - const fileSize = maxFileSize * 1e6 + 1 - const bufferStream = new stream.PassThrough() - bufferStream.end(Buffer.alloc(fileSize)) - const file = { - filename: 'manuscript.pdf', - stream: bufferStream, - mimetype: 'application/pdf', - } - await expect( - Mutation.uploadManuscript( - {}, - { id, file, fileSize }, - { user: profileId }, - ), - ).rejects.toThrow(`File size shouldn't exceed ${maxFileSize}MB`) - }) - }) - - describe('deleteManuscript', () => { - it("fails if manuscript doesn't belong to user", async () => { - const blankManuscript = Manuscript.new() - blankManuscript.createdBy = userId - const manuscript = await Manuscript.save(blankManuscript) - - await expect( - Mutation.deleteManuscript( - {}, - { id: manuscript.id }, - { user: badProfileId }, - ), - ).rejects.toThrow('Manuscript not found') - }) - - it('removes manuscript from database', async () => { - const blankManuscript = Manuscript.new() - blankManuscript.createdBy = userId - const manuscript = await Manuscript.save(blankManuscript) - await Mutation.deleteManuscript( - {}, - { id: manuscript.id }, - { user: profileId }, - ) - - const manuscripts = await Manuscript.all(userId) - expect(manuscripts).toEqual([]) - }) - }) -}) -async function waitforEmails(NUM_EMAILS) { - for (let i = 0; i < 100 && mailer.getMails().length < NUM_EMAILS; i += 1) { - // eslint-disable-next-line no-await-in-loop - await new Promise(resolve => setTimeout(resolve, 10)) - } -} */ diff --git a/server/xpub-server/index.js b/server/xpub-server/index.js index 39898cdaa..cc800fec3 100644 --- a/server/xpub-server/index.js +++ b/server/xpub-server/index.js @@ -8,6 +8,7 @@ const entities = [ 'file', 'identity', 'manuscript', + 'journal', 'note', 'team', 'role', -- GitLab