Skip to content
Merged

v0.21.0 #1082

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ad4f93c
Merge pull request #1059 from PathwayCommons/v0.20.0
jvwong Mar 30, 2022
d6ac2f4
Use ISO_8601 representation for lastmod
jvwong Apr 4, 2022
94586cc
Merge pull request #1063 from PathwayCommons/iss1061_sitemap-lastmod
jvwong Apr 4, 2022
d7dcabc
Merge pull request #1063 from PathwayCommons/iss1061_sitemap-lastmod
jvwong Apr 4, 2022
6e37e13
Merge pull request #1059 from PathwayCommons/v0.20.0
jvwong Mar 30, 2022
b62b6c9
Merge branch 'unstable' of https://github.com/PathwayCommons/factoid …
jvwong Apr 4, 2022
35686f2
Add the config variable for the public ORCID API
jvwong Apr 4, 2022
0e73815
Add the ORCID website url
jvwong Apr 4, 2022
a24d097
- utilize PubMed ORCID information
jvwong Apr 5, 2022
e29cffd
Catch any mangled ORCIDs in PubMed records correctly.
jvwong Apr 6, 2022
5f6c0a9
Uncomment
jvwong Apr 11, 2022
365ea65
Document the new methods
jvwong Apr 11, 2022
8b8416d
Merge pull request #1065 from PathwayCommons/iss1064_orcid-refactoring
jvwong Apr 13, 2022
b56c4a6
Make the cron update the author profiles.
jvwong Apr 13, 2022
276e3a2
Merge pull request #1068 from PathwayCommons/iss1067_cron-author-prof…
jvwong Apr 19, 2022
98c40b6
Never search an empty string
jvwong Aug 31, 2022
2c63694
Id mapping declares the new db prefix.
jvwong Sep 1, 2022
590d59a
Merge pull request #1076 from PathwayCommons/iss1075_search-empty
maxkfranz Sep 1, 2022
010d657
Merge pull request #1077 from PathwayCommons/iss1073_xref-biopax
jvwong Sep 1, 2022
a78f2bc
Reply to Tweet with paper link
jvwong Sep 1, 2022
849d132
Merge pull request #1078 from PathwayCommons/iss1041_article-link
maxkfranz Sep 2, 2022
1c17d56
0.21.0
jvwong Sep 2, 2022
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
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,5 @@
"engines": {
"node": ">=10.0.0"
},
"version": "0.20.0"
"version": "0.21.0"
}
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
10 changes: 6 additions & 4 deletions src/client/components/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ class Home extends Component {

if (query) {
this.activateSearchMode();

this.debouncedSearch();
} else {
this.setState({ searchDocs: this.state.allDocs });
Expand All @@ -261,6 +261,8 @@ class Home extends Component {

search() {
const q = this.state.query;
if( !q ) return;

const waitForFetch = () => this.docSearchFetch;
const doQuery = () => this.docSearch.search(q);

Expand Down Expand Up @@ -324,17 +326,17 @@ class Home extends Component {
let authorNames = authorList.map( a => a.name );
const id = doc.id;
const link = doc.publicUrl;

if( authorNames.length > 3 ){
authorNames = authorNames.slice(0, 2).concat([ '...', authorNames[authorNames.length - 1] ]);
}

const figureDiv = h('div.home-search-doc-figure', {
style: {
backgroundImage: `url('/api/document/${id}.png')`
}
});

return h('div.home-search-doc', [
h('a', {
href: link,
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
138 changes: 90 additions & 48 deletions src/server/routes/api/document/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ 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,
DOI_LINK_BASE_URL
} from '../../../../config';

import { ENTITY_TYPE } from '../../../../model/element/entity-type';
Expand Down Expand Up @@ -207,12 +209,13 @@ const mapToUniprotIds = docTemplate => {
return Promise.resolve();
}

const UNIPROT_DB_PREFIX = 'uniprot';
const opts = {
id: [
id
],
dbfrom: dbPrefix,
dbto: 'uniprot'
dbto: UNIPROT_DB_PREFIX
};

return fetch( GROUNDING_SEARCH_BASE_URL + '/map', {
Expand All @@ -228,6 +231,7 @@ const mapToUniprotIds = docTemplate => {
if ( dbXref ) {
xref.id = dbXref.id;
xref.db = dbXref.db;
xref.dbPrefix = opts.dbto;
}
} );
};
Expand Down Expand Up @@ -344,60 +348,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 Expand Up @@ -1436,6 +1463,19 @@ const tweetDoc = ( doc, text ) => {
.then( tweet => doc.setTweetMetadata( tweet ) );
};

// reply to document tweet with article link
const tweetArticleReply = async doc => {
const { doi } = doc.citation();
if( !doi || !doc.hasTweet() ) return;

const { id_str: in_reply_to_status_id } = doc.tweetMetadata();
const url = `${DOI_LINK_BASE_URL}${doi}`;
const status = `Read the paper: ${url}`;

await twitterClient.post( 'statuses/update', { status, in_reply_to_status_id } );
return doc;
};

const tryTweetingDoc = async ( doc, text ) => {

const shouldTweet = doc => {
Expand All @@ -1449,6 +1489,7 @@ const tryTweetingDoc = async ( doc, text ) => {
try {
if( !text ) text = truncateString( doc.toText(), MAX_TWEET_LENGTH );
await tweetDoc( doc, text );
await tweetArticleReply( doc );
} catch ( e ) {
logger.error( `Error attempting to Tweet: ${JSON.stringify(e)}` ); //swallow
}
Expand Down Expand Up @@ -2455,6 +2496,7 @@ const getRelatedPapersForNetwork = async doc => {
export default http;
export { getDocumentJson,
loadTables, loadDoc, fillDocArticle, updateRelatedPapers,
fillDocAuthorProfiles,
generateSitemap,
getDocuments, getSBGN, getBioPAX
}; // allow access so page rendering can get the same data as the rest api
3 changes: 2 additions & 1 deletion src/server/routes/api/document/update.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logger from '../../../logger';
import { DEMO_SECRET, DOCUMENT_CRON_CREATED_AGE_DAYS, DOCUMENT_CRON_REFRESH_ENABLED, DOCUMENT_CRON_UNEDITED_DAYS } from '../../../../config';
import { loadTables, loadDoc, fillDocArticle } from './index';
import { loadTables, loadDoc, fillDocArticle, fillDocAuthorProfiles } from './index';
import Document from '../../../../model/document';

const DOCUMENT_STATUS_FIELDS = Document.statusFields();
Expand Down Expand Up @@ -92,6 +92,7 @@ const updateArticle = async () => {
let chunks = chunkify( docs );
for( const chunk of chunks ){
await Promise.all( chunk.map( fillDocArticle ) );
await Promise.all( chunk.map( fillDocAuthorProfiles ) );
}
lastUpdateTime( Date.now() );

Expand Down
2 changes: 1 addition & 1 deletion src/server/sitemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class Sitemap {
this.docs.forEach( doc => {
const url = eltFactory( 'url' );
const loc = eltFactory( 'loc', {}, `${BASE_URL}${doc.publicUrl}` );
const lastmod = eltFactory( 'lastmod', {}, `${doc.lastEditedDate}` );
const lastmod = eltFactory( 'lastmod', {}, `${doc.lastEditedDate.toISOString()}` );
const changefreq = eltFactory( 'changefreq', {}, this.changefreq );
const priority = eltFactory( 'priority', {}, this.priority );
const image = getImageElt( doc );
Expand Down
Loading