Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Services:
- `INDRA_DB_BASE_URL` : url for [INDRA (Integrated Network and Dynamical Reasoning Assembler)](https://indralab.github.io/)
- `INDRA_ENGLISH_ASSEMBLER_URL` : url for service that assembles INDRA statements into models
- `SEMANTIC_SEARCH_BASE_URL` : url for [semantic-search](https://github.com/PathwayCommons/semantic-search) web service
- `ORCID_BASE_URL` : url for [ORCID](https://orcid.org/) website
- `ORCID_PUBLIC_API_BASE_URL` : url for version of [ORCID](https://orcid.org/) public API
- `NO_ABSTRACT_HANDLING` : labels directing how to sort documents missing query text. 'text' (default): autogenerate text from templates; 'date': sort by date and ignore text.

Links:
Expand Down
3 changes: 2 additions & 1 deletion src/client-env-vars.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
"DEMO_CAN_BE_SHARED_MULTIPLE_TIMES",
"SAMPLE_DOC_ID",
"EMAIL_ADDRESS_INFO",
"MAX_WAIT_TWEET"
"MAX_WAIT_TWEET",
"ORCID_BASE_URL"
]
12 changes: 7 additions & 5 deletions src/client/components/editor/credits.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import h from 'react-hyperscript';
import { Component } from 'react';
import _ from 'lodash';
import { ORCID_BASE_URL } from '../../../config';
import { findOrcidIdentifier } from '../../../util/pubmed';

export class Credits extends Component {
constructor(props){
Expand All @@ -17,16 +19,16 @@ export class Credits extends Component {

const profiles = document.authorProfiles() || [];
const isContributingAuthor = author => author.name === authorName;
const authorLink = _.get(profiles.find(isContributingAuthor), ['orcid']);
const orcid = findOrcidIdentifier( _.get(profiles.find(isContributingAuthor), ['orcid']) ); // Backwards compatible with URI

return h('div.editor-credits', [
`Article summary created by `,
(authorLink ?
h('a.plain-link', { href: authorLink, target: '_blank' }, authorName)
(orcid ?
h('a.plain-link', { href: `${ORCID_BASE_URL}${orcid}`, target: '_blank' }, authorName)
:
h('span', authorName)
h('span', authorName)
),
authorLink ? h('span', [
orcid ? h('span', [
h('span', ' '),
h('i.icon.icon-orcid')
]) : null
Expand Down
9 changes: 5 additions & 4 deletions src/client/components/editor/info-panel.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import h from 'react-hyperscript';
import { Component } from 'react';
import { DOI_LINK_BASE_URL, PUBMED_LINK_BASE_URL, GOOGLE_SCHOLAR_BASE_URL } from '../../../config';
import { DOI_LINK_BASE_URL, PUBMED_LINK_BASE_URL, GOOGLE_SCHOLAR_BASE_URL, ORCID_BASE_URL } from '../../../config';
// import { Carousel, CAROUSEL_CONTENT } from '../carousel';
import { makeClassList } from '../../dom';
import ElementInfo from '../element-info/element-info';
import RelatedPapers from '../related-papers';
import _ from 'lodash';
import Credits from './credits';
import { findOrcidIdentifier } from '../../../util/pubmed';

export class InfoPanel extends Component {
constructor(props){
Expand Down Expand Up @@ -75,10 +76,10 @@ export class InfoPanel extends Component {
h('div.editor-info-flags', [ retractFlag ]),
h('div.editor-info-title', title),
h('div.editor-info-authors', _.flatten(authorProfiles.map((a, i) => {
let orcidUri = a.orcid;
if ( orcidUri ) {
let orcid = findOrcidIdentifier( a.orcid ); // Backwards compatible with URI
if ( orcid ) {
return [
h('a.editor-info-author.plain-link', { target: '_blank', href: orcidUri }, a.name),
h('a.editor-info-author.plain-link', { target: '_blank', href: `${ORCID_BASE_URL}${orcid}` }, a.name),
h('i.icon.icon-orcid.editor-info-author-orcid'),
i !== authorProfiles.length - 1 ? h('span.editor-info-author-spacer', ', ') : null
];
Expand Down
2 changes: 2 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export const NCBI_EUTILS_API_KEY = env('NCBI_EUTILS_API_KEY', 'b99e10ebe0f90d815
export const INDRA_DB_BASE_URL = env('INDRA_DB_BASE_URL', 'https://db.indra.bio/');
export const INDRA_ENGLISH_ASSEMBLER_URL = env('INDRA_ENGLISH_ASSEMBLER_URL', 'http://api.indra.bio:8000/assemblers/english');
export const SEMANTIC_SEARCH_BASE_URL = env('SEMANTIC_SEARCH_BASE_URL', 'https://main.semanticsearch.baderlab.org/');
export const ORCID_BASE_URL = env('ORCID_BASE_URL', 'https://orcid.org/');
export const ORCID_PUBLIC_API_BASE_URL = env('ORCID_PUBLIC_API_BASE_URL', 'https://pub.orcid.org/v3.0/');

// Links
export const UNIPROT_LINK_BASE_URL = env('UNIPROT_LINK_BASE_URL', 'http://www.uniprot.org/uniprot/');
Expand Down
118 changes: 71 additions & 47 deletions src/server/routes/api/document/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import { BASE_URL,
EMAIL_ADDRESS_ADMIN,
BULK_DOWNLOADS_PATH,
BIOPAX_DOWNLOADS_PATH,
BIOPAX_IDMAP_DOWNLOADS_PATH
BIOPAX_IDMAP_DOWNLOADS_PATH,
ORCID_PUBLIC_API_BASE_URL
} from '../../../../config';

import { ENTITY_TYPE } from '../../../../model/element/entity-type';
Expand Down Expand Up @@ -344,60 +345,83 @@ const fillDocArticle = async doc => {
}
};

const makeOrcidKey = ( s1, s2 ) => s1 + '_' + s2;

const getOrcidUriMap = async doi => {
let url = `https://pub.orcid.org/v3.0/csv-search?q=`
+ `doi%3D${doi}`;

const textToMap = text => {
let lines = text.split(/\r\n|\r|\n/);
let obj = {};
lines.forEach( ( line, i ) => {
if ( i == 0 ) {
return;
}
let vals = line.split(',');
let orcidUri = 'https://orcid.org/' + vals[ 0 ];
let givenNames = vals[ 2 ];
let familyName = vals[ 3 ];
/**
* searchOrcid
* Search the ORCID API using an article DOI
*
* @param {string} doi article DOI
* @returns A unique set of search {@link https://info.orcid.org/documentation/integration-guide/orcid-record/ records}
*/
const searchOrcid = async doi => {
const getDoiSearchUrl = doi => `${ORCID_PUBLIC_API_BASE_URL}expanded-search?q=doi-self:${doi}`;
const fetchJson = url => fetch( url, { headers: { 'accept': 'application/json' } } );
const toJson = res => res.json();
const fetchAllJson = async urls => {
const responses = await Promise.all( urls.map( fetchJson ) );
return await Promise.all( responses.map( toJson ) );
};
const mergeExpandedResults = searchResults => {
const getSearchResult = response => response['expanded-result']; //possibly null
let records = _.compact( searchResults.map( getSearchResult ) );
return _.unionBy( ...records, 'orcid-id' );
};

if ( !givenNames || !familyName ) {
return;
}
try {
// DOIs can be set by clients, with varying case (e.g. eLife vs elife)
let rawUrl = getDoiSearchUrl( doi );
let normalizedUrl = getDoiSearchUrl( doi.toLowerCase() );
let urls = [ rawUrl, normalizedUrl ];

let key = makeOrcidKey( givenNames, familyName );
obj[ key ] = orcidUri;
} );
let expandedSearchResults = await fetchAllJson( urls );
let searchRecords = mergeExpandedResults( expandedSearchResults );
return searchRecords;

return obj;
};
} catch ( err ) {
logger.error( `Error finding Orcid URI: ${err.message}`);
return null;
}
};

return fetch( url, { method: 'GET', headers: { 'accept': 'text/csv' } } )
.then( res => res.text() )
.then( textToMap )
.catch( error => {
logger.error( `Error finding Orcid URI: ${error.message}`);
return null;
} );
const findAllIndexes = ( collection, key, val ) => {
var indexes = [], i = -1;
while ( ( i = _.findIndex( collection, [ key, val ], i + 1 ) ) != -1 ){
indexes.push( i );
}
return indexes;
};

/**
* fillDocAuthorProfiles
* Supplement PubMed author ORCIDs from ORCID itself.
* Match by last name when unique, else by first and last names.
*
* @param {Object} doc Document
* @returns An array of author profile information
*/
const fillDocAuthorProfiles = async doc => {
const citation = doc.citation();
const { authors, doi } = citation;

let orcidUrisMap = await getOrcidUriMap( doi );

orcidUrisMap = orcidUrisMap || null;

let authorProfiles = authors.authorList.map( a => {
let key = makeOrcidKey( a.ForeName, a.LastName );
let orcid = orcidUrisMap[ key ] || null;
let authorProfile = _.assignIn({}, a, { orcid });
return authorProfile;
});


const { authors: { authorList }, doi } = citation;

let orcidRecords = await searchOrcid( doi );
let authorProfiles = authorList
.map( author => {
let authorProfile = _.assign( {}, author );
const { orcid, ForeName, LastName } = author;
if( orcid == null ){
let match;
const byLastName = [ 'family-names', LastName ];
const byFirstLastNames = o => LastName == o['family-names'] && ForeName == o['given-names'];
let authorListIndices = findAllIndexes( authorList, 'LastName', LastName );
let lastNameIsUnique = authorListIndices.length == 1;

// Find by last name alone, if it is unique
match = lastNameIsUnique ?
_.find( orcidRecords, byLastName ) :
_.find( orcidRecords, byFirstLastNames );
if( match ) _.set( authorProfile, ['orcid'], match['orcid-id'] );
}
return authorProfile;
});
await doc.authorProfiles( authorProfiles );
};

Expand Down
30 changes: 28 additions & 2 deletions src/util/pubmed.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,31 @@ const getAuthorAbbrev = AuthorList => {

const getContacts = AuthorList => AuthorList.map( getContact ).filter( contact => !_.isEmpty( _.get( contact, 'email' ) ) );

/**
* findOrcidIdentifier
* Helper to extract an ORCID accession from a string
*
* @param {string} raw the string to examine
* @returns ORCID accession, possibly null
*/
const findOrcidIdentifier = raw => {
let orcidId = null;
if( raw ){
const ORCID_REGEX = /\d{4}-\d{4}-\d{4}-\d{3}(\d|X)/g;
const matches = raw.match( ORCID_REGEX );
const hasMatch = !_.isEmpty( matches );
if( hasMatch ) orcidId = _.head( matches );
}
return orcidId;
};

const getAuthorOrcid = Author => {
const isOrcId = id => id['Source'] === 'ORCID';
const Identifier = _.get( Author, ['Identifier'] );
let raw = _.get( _.find( Identifier, isOrcId ), ['id'] );
return findOrcidIdentifier( raw );
};

const getAuthorList = AuthorList => {
return AuthorList.map( Author => {
const { ForeName, LastName } = getAuthorNameParts( Author );
Expand All @@ -103,7 +128,8 @@ const getAuthorList = AuthorList => {
LastName,
email: _.head( getEmail( Author ) ) || null,
abbrevName: getAbbrevAuthorName( Author ),
isCollectiveName: !_.isNull( _.get( Author, 'CollectiveName' ) )
isCollectiveName: !_.isNull( _.get( Author, 'CollectiveName' ) ),
orcid: getAuthorOrcid( Author )
};
});
};
Expand Down Expand Up @@ -276,4 +302,4 @@ class ArticleIDError extends Error {
}
}

export { getPubmedCitation, createPubmedArticle, ArticleIDError };
export { getPubmedCitation, createPubmedArticle, ArticleIDError, findOrcidIdentifier };