diff --git a/app/components/citation-search/PubMedSearch.jsx b/app/components/citation-search/PubMedSearch.jsx index 13ebd3c1eb4c10d03b83f237577819bb0b7a377c..5d3bfbcb054faf363e426c8f461e153f73da53bd 100755 --- a/app/components/citation-search/PubMedSearch.jsx +++ b/app/components/citation-search/PubMedSearch.jsx @@ -54,9 +54,9 @@ class PubMedSearch extends React.Component { pmcid: null, inEPMC: false, unmatched: - this.props.metadata && - (!this.props.metadata.articleIds || - this.props.metadata.articleIds.length === 0), + this.props.manuscript && + (!this.props.manuscript.meta.articleIds || + this.props.manuscript.meta.articleIds.length === 0), pubProvided: false, } this.onSearch = this.onSearch.bind(this) @@ -65,8 +65,15 @@ class PubMedSearch extends React.Component { this.onSelected = this.onSelected.bind(this) this.handleUnmatchedInfo = this.handleUnmatchedInfo.bind(this) } - handleUnmatchedInfo(metadata) { - this.props.citationData(metadata) + handleUnmatchedInfo(unm) { + if (unm.journal && unm.journal.meta.pmcStatus) { + this.setState({ pubProvided: unm.journal.journalTitle }) + } else { + const citationData = unm + citationData.journalId = citationData.journal.id + delete citationData.journal + this.props.citationData(citationData) + } } onQueryChange(event) { this.setState({ @@ -190,7 +197,7 @@ class PubMedSearch extends React.Component { <UnmatchedCitation journal={journalInfo} note={note ? note.content : ''} - title={meta.title} + title={meta && meta.title} unmatchedInfo={this.handleUnmatchedInfo} /> </Page> diff --git a/app/components/citation-search/UnmatchedCitation.jsx b/app/components/citation-search/UnmatchedCitation.jsx index cfa26696b25bf7e4e0857b8866047a64a22fcb86..30648c36e8376f8158bfd569a06e84c652f60acc 100755 --- a/app/components/citation-search/UnmatchedCitation.jsx +++ b/app/components/citation-search/UnmatchedCitation.jsx @@ -1,7 +1,9 @@ import React from 'react' +import { ApolloConsumer } from 'react-apollo' import * as Joi from 'joi' -import { debounce, omit } from 'lodash' +import { debounce } from 'lodash' import { Button, ErrorText, H2, TextField } from '@pubsweet/ui' +import { SEARCH_JOURNALS } from './operations' import { Buttons, HTMLString, @@ -12,35 +14,29 @@ import { } from '../ui' export default class UnmatchedCitation extends React.Component { - constructor(props) { - super(props) - this.state = { - loading: false, - title: this.props.title, - query: this.props.journal ? this.props.journal.journalTitle : '', - journal: this.props.journal, - note: this.props.note, - enabled: this.props.title && this.props.journal, - journals: [], - titleMessage: '', - journalMessage: '', - } - this.onQueryChange = this.onQueryChange.bind(this) - this.onSearch = debounce(this.onSearch.bind(this), 100) - this.enable = this.enable.bind(this) - this.submitData = this.submitData.bind(this) - this.onTitleChange = this.onTitleChange.bind(this) - this.onJournalChange = this.onJournalChange.bind(this) + state = { + loading: false, + title: this.props.title, + query: this.props.journal ? this.props.journal.journalTitle : '', + journal: this.props.journal, + note: this.props.note, + enabled: this.props.title && this.props.journal, + journals: [], + titleMessage: '', + journalMessage: '', } - enable() { + enable = () => { const validTitle = Joi.validate(this.state.title, Joi.string().required()) const validJournal = Joi.validate( this.state.journal, Joi.object({ - title: Joi.string().required(), - longTitle: Joi.string(), - nlmuniqueid: Joi.string(), + id: Joi.string(), + journalTitle: Joi.string().required(), + meta: Joi.object({ + pmcStatus: Joi.boolean(), + }), }), + { allowUnknown: true }, ) if (!validTitle.error && !validJournal.error) { this.setState({ enabled: true }) @@ -51,75 +47,28 @@ export default class UnmatchedCitation extends React.Component { }) } } - submitData() { + submitData = () => { if (this.state.enabled) { - const metadata = { - title: this.state.title, + const citationData = { + meta: { + title: this.state.title, + }, } - if (this.state.journal.nlmuniqueid) { - metadata.journalMeta = omit(this.state.journal, 'longTitle') + if (this.state.journal.id) { + citationData.journal = this.state.journal } else { - metadata.customMeta = { - unmatchedJournal: this.state.journal.title, - } + citationData.meta.unmatchedJournal = this.state.journal.journalTitle } - if (this.state.note) { - metadata.citationNote = { + /* if (this.state.note) { + citationD.citationNote = { notesType: 'user citation', content: this.state.note, } - } - this.props.unmatchedInfo(metadata) - } - } - onQueryChange(e) { - const query = e.target.value - this.setState({ - query: e ? query : '', - journals: [], - loading: true, - }) - if (query.trim().length > 0) { - this.onSearch(query) - } - } - async onSearch(query) { - const eSearch = `/eutils/esearch?db=nlmcatalog&term=${query}*[ta]%20AND%20ncbijournals[filter]&retstart=0` - const response = await fetch(eSearch, { - headers: new Headers({ - Authorization: `Bearer ${window.localStorage.getItem('token')}`, - }), - }) - const json = await response.json() - const ids = json.esearchresult.idlist - const hitcount = json.esearchresult.count - const newState = { loading: false } - if (hitcount > 0) { - const eFetch = `/eutils/efetch?db=nlmcatalog&id=${ids.join()}` - const summary = await fetch(eFetch, { - headers: new Headers({ - Authorization: `Bearer ${window.localStorage.getItem('token')}`, - }), - }) - const xml = await summary.text() - const parser = new DOMParser() - const xmlDoc = parser.parseFromString(xml, 'text/xml') - const records = Array.from(xmlDoc.documentElement.children) - const journals = records.reduce((list, record) => { - if (record.querySelector('MedlineTA')) { - list.push({ - longTitle: record.querySelector('TitleMain Title').innerHTML, - title: record.querySelector('MedlineTA').innerHTML, - nlmuniqueid: record.querySelector('NlmUniqueID').innerHTML, - }) - } - return list - }, []) - newState.journals = journals + } */ + this.props.unmatchedInfo(citationData) } - this.setState(newState) } - onTitleChange(event) { + onTitleChange = event => { this.setState( { titleMessage: '', @@ -130,12 +79,13 @@ export default class UnmatchedCitation extends React.Component { }, ) } - onJournalChange(journal) { + onJournalSelect = journal => { + const { id, journalTitle, meta } = journal this.setState( { journalMessage: '', - journal, - query: journal.title, + journal: { id, journalTitle, meta }, + query: journal.journalTitle, }, () => { this.enable() @@ -157,49 +107,73 @@ export default class UnmatchedCitation extends React.Component { {this.state.titleMessage && ( <ErrorText>{this.state.titleMessage}</ErrorText> )} - <SearchSelect - invalidTest={this.state.journalMessage} - label="Search for Journal" - onInput={this.onQueryChange} - optionsOnChange={this.onJournalChange} - placeholder="Journal title abbreviation" - query={this.state.query} - singleSelect - > - {this.state.loading && ( - <Loading> - <LoadingIcon /> - </Loading> - )} - {this.state.journals.map(journal => ( - <SearchSelect.Option - data-option={journal} - key={journal.title + journal.id} - propKey={journal.title + journal.id} - > - <HTMLString string={journal.longTitle} /> [ - <em> - <HTMLString string={journal.title} /> - </em> - ] - </SearchSelect.Option> - ))} - {this.state.query.trim().length > 0 && ( - <SearchSelect.Option - data-option={{ title: this.state.query }} - key="submit" - propKey="submit" - > - <em> - <HTMLString - string={`Submit '${ - this.state.query - }' as your journal name`} - /> - </em> - </SearchSelect.Option> - )} - </SearchSelect> + <ApolloConsumer> + {client => { + const searchJournals = debounce(async () => { + const { query } = this.state + const { data } = await client.query({ + query: SEARCH_JOURNALS, + variables: { query }, + }) + this.setState({ + journals: data.alphaJournals, + loading: false, + }) + }, 100) + const onQueryChange = e => { + const query = e.target.value + this.setState({ + query: e ? query : '', + journals: [], + loading: true, + }) + if (query.trim().length > 0) { + searchJournals() + } + } + return ( + <SearchSelect + invalidTest={this.state.journalMessage} + label="Search for journal title" + onInput={onQueryChange} + optionsOnChange={this.onJournalSelect} + placeholder="The journal title" + query={this.state.query} + singleSelect + > + {this.state.loading && ( + <Loading> + <LoadingIcon /> + </Loading> + )} + {this.state.journals.map(journal => ( + <SearchSelect.Option + data-option={journal} + key={journal.id} + propKey={journal.id} + > + <HTMLString string={journal.journalTitle} /> + </SearchSelect.Option> + ))} + {this.state.query.trim().length > 0 && ( + <SearchSelect.Option + data-option={{ journalTitle: this.state.query }} + key="submit" + propKey="submit" + > + <em> + <HTMLString + string={`Submit '${ + this.state.query + }' as your journal name`} + /> + </em> + </SearchSelect.Option> + )} + </SearchSelect> + ) + }} + </ApolloConsumer> {this.state.journalMessage && ( <ErrorText>{this.state.journalMessage}</ErrorText> )} diff --git a/app/components/citation-search/operations.js b/app/components/citation-search/operations.js index c11190531510d0192ad313d8cb83e3d4250e005f..1c14068135924250b4f30c40bbcb6ed08894dbf1 100644 --- a/app/components/citation-search/operations.js +++ b/app/components/citation-search/operations.js @@ -6,39 +6,19 @@ export const JOURNAL_INFO = gql` 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) { +export const SEARCH_JOURNALS = gql` + query($query: String!) { + alphaJournals(query: $query) { id + journalTitle meta { - notes { - id - notesType - content - } + pmcStatus } } } diff --git a/app/components/ui/molecules/SearchSelect.jsx b/app/components/ui/molecules/SearchSelect.jsx index b7580cb749078708efe29fb45637effbdc76e4b4..3473b97eb78d87c570af677de805e6cbe1f9b283 100755 --- a/app/components/ui/molecules/SearchSelect.jsx +++ b/app/components/ui/molecules/SearchSelect.jsx @@ -99,6 +99,7 @@ const SearchInput = styled.input` border: none; background-color: transparent; padding: 0 ${th('gridUnit')}; + box-sizing: border-box; font-family: inherit; font-size: inherit; &:focus { @@ -108,12 +109,13 @@ const SearchInput = styled.input` const DropdownIcon = styled(Icon)` border-left: ${th('borderWidth')} ${th('borderStyle')} ${borderColor}; background-color: ${th('colorBackgroundHue')}; - height: calc((${th('gridUnit')} * 6) - (${th('borderWidth')} * 2)); + height: calc(100% - (${th('borderWidth')} * 4)); + box-sizing: border-box; display: inline-flex; align-items: center; position: absolute; - right: ${th('borderWidth')}; - top: ${th('borderWidth')}; + right: calc(${th('borderWidth')} * 2); + top: calc(${th('borderWidth')} * 2); ${override('ui.Select.DropdownIcon')}; ${override('ui.SearchSelect.DropdownIcon')}; ` diff --git a/server/xpub-model/entities/journal/data-access.js b/server/xpub-model/entities/journal/data-access.js index dc623e0b6bf4356c684bf8ed47dc237c65e75b0d..7f5ccf2b02b92739d8ca8ffbadfa7622a6cafb1a 100644 --- a/server/xpub-model/entities/journal/data-access.js +++ b/server/xpub-model/entities/journal/data-access.js @@ -61,16 +61,31 @@ class Journal extends EpmcBaseModel { } static async selectByNlmId(nlmId) { + const row = await runQuery( + buildQuery + .select('journal.*') + .from('journal') + .where('meta,nlmuniqueid', nlmId) + .first(), + ) + if (!row) { + throw new Error('journal not found') + } + return rowToEntity(row) + } + + static async searchByTitle(query) { const rows = await runQuery( buildQuery .select('journal.*') .from('journal') - .where('meta,nlmuniqueid', nlmId), + .where('journalTitle', 'ILIKE', `${query}%`) + .orderBy('journalTitle'), ) if (!rows) { throw new Error('journal not found') } - return rowToEntity(rows[0]) + return rowToEntity(rows) } static async selectAll() { diff --git a/server/xpub-model/entities/journal/index.js b/server/xpub-model/entities/journal/index.js index 832a196d624d5fb098ffb72367b209bd878d4a0d..0aaf5cd02a3b149105d751d6ea45a8707ed9b5b9 100755 --- a/server/xpub-model/entities/journal/index.js +++ b/server/xpub-model/entities/journal/index.js @@ -24,6 +24,7 @@ const JournalManager = { return { ...journal, id } }, selectWithNLM: async nlmId => Journal.selectByNlmId(nlmId), + searchByTitle: async query => Journal.searchByTitle(query), } module.exports = JournalManager diff --git a/server/xpub-server/entities/journal/resolvers.js b/server/xpub-server/entities/journal/resolvers.js index 415fcf7c4ebb3d6b594eee1196fd30b3018cff2f..4ffd71b0c0003fb0f39f64a4ce2ad013cb49b448 100644 --- a/server/xpub-server/entities/journal/resolvers.js +++ b/server/xpub-server/entities/journal/resolvers.js @@ -11,6 +11,13 @@ const resolvers = { return JournalManager.selectWithNLM(nlmId) }, + async alphaJournals(_, { query }, { user }) { + if (!user) { + throw new Error('You are not authenticated!') + } + + return JournalManager.searchByTitle(query) + }, }, } diff --git a/server/xpub-server/entities/journal/typeDefs.graphqls b/server/xpub-server/entities/journal/typeDefs.graphqls index 4b5b4c416fad50770dde60238a2c4667abd739d7..b5cc564c549acd67fb3fba3b4ea67accb99c2aaf 100644 --- a/server/xpub-server/entities/journal/typeDefs.graphqls +++ b/server/xpub-server/entities/journal/typeDefs.graphqls @@ -1,3 +1,4 @@ extend type Query { selectWithNLM(nlmId: String!): Journal! -} \ No newline at end of file + alphaJournals(query: String!): [Journal]! +}