/* eslint-disable no-alert, no-console */ import algoliasearch from 'algoliasearch' const getFacetsChildrenMap = (childFacets, numberOfLayers) => { const mapping = {} childFacets.forEach((facet) => { const info = facet.split('.'); if (info.length !== numberOfLayers) { return; } const pathName = facet.substring(0, facet.lastIndexOf('.')); const name = info[info.length - 1]; if (Object.keys(mapping).includes(pathName)) { mapping[pathName].push(name); } else { mapping[pathName] = [name]; } }); return mapping; } // export `createAlgoliaClient` to use it in page components export class AlgoliaClient { constructor(algoliaId, algoliaKey, PENNSIEVE_API_LOCATION = 'https://api.pennsieve.io') { this.client = algoliasearch( algoliaId, algoliaKey ) this.PENNSIEVE_API_LOCATION = PENNSIEVE_API_LOCATION this.anatomyFacetLabels = [] } initIndex(ALGOLIA_INDEX) { this.index = this.client.initIndex(ALGOLIA_INDEX); } getAlgoliaFacets(propPathMapping) { const facetPropPaths = propPathMapping.map(facet => facet.facetPropPath) const facetSubpropPaths = propPathMapping.map(item => item.facetSubpropPath) const facetSubsubpropPaths = propPathMapping.map( item => item.facetSubsubpropPath).filter( i => i !== undefined ) return this.index .search('', { sortFacetValuesBy: 'alpha', facets: facetPropPaths.concat(facetSubpropPaths).concat(facetSubsubpropPaths), }) .then(response => { let facetData = [] let facetId = 0 facetPropPaths.map((facetPropPath) => { const parentFacet = propPathMapping.find(item => item.facetPropPath == facetPropPath) var children = [] const responseFacets = response.facets if (responseFacets === undefined) {return} const responseFacetChildren = responseFacets[facetPropPath] == undefined // if no facets, return empty object ? {} : responseFacets[facetPropPath] const allSubfacets = parentFacet && responseFacets[parentFacet.facetSubpropPath] ? Object.keys(responseFacets[parentFacet.facetSubpropPath]) : [] const allSubsubfacets = (parentFacet && parentFacet.facetSubsubpropPath && responseFacets[parentFacet.facetSubsubpropPath]) ? Object.keys(responseFacets[parentFacet.facetSubsubpropPath]) : [] const subFacetsMap = getFacetsChildrenMap(allSubfacets, 2); const subSubFacetsMap = getFacetsChildrenMap(allSubsubfacets, 3); // Loop through all subfacets and find the ones that are children of the current facet Object.keys(responseFacetChildren).map(facet => { const childrenSubfacets = []; if (Object.keys(subFacetsMap).includes(facet)) { subFacetsMap[facet].forEach((label) => { const fullPath = `${facet}.${label}` const childrenSubsubfacets = [] if (Object.keys(subSubFacetsMap).includes(fullPath)) { subSubFacetsMap[fullPath].forEach((childLabel) => { childrenSubsubfacets.push( { label: childLabel, id: facetId++, facetPropPath: `${parentFacet ? parentFacet.facetSubsubpropPath : undefined}` } ) }); //REMOVE ME LATER: This is a hack to add an extra item for subsubcategory if (fullPath === "nerves and ganglia.dorsal root ganglion") { childrenSubsubfacets.push( { label: "Non specific", id: facetId++, facetPropPath: `${parentFacet ? parentFacet.facetSubsubpropPath : undefined}` } ) } } childrenSubfacets.push( { label, id: facetId++, facetPropPath: `${parentFacet ? parentFacet.facetSubpropPath : undefined}`, children: childrenSubsubfacets.length ? childrenSubsubfacets : undefined, } ); }) } let newChild = { label: facet, id: facetId++, facetPropPath: facetPropPath } if (childrenSubfacets.length > 0) { newChild.children = childrenSubfacets } children.push(newChild) }) if (children.length > 0) { facetData.push({ label: parentFacet ? parentFacet.label : '', id: facetId++, children: children, key: facetPropPath }) } }) return facetData }) } // Returns all DOIs of all versions for a given discover dataset _discoverAllDois(discoverId, PENNSIEVE_API_LOCATION = 'https://api.pennsieve.io') { return new Promise(resolve => { fetch(`${PENNSIEVE_API_LOCATION}/discover/datasets/${discoverId}/versions`).then(r => r.json()).then(dataset => { resolve(dataset.map(version => version.doi)) }) }) } // Get all dois given a list of discoverIds _expandDois(discoverIds, PENNSIEVE_API_LOCATION = 'https://api.pennsieve.io') { return new Promise(resolve => { let promiseList = discoverIds.map(discoverId => this._discoverAllDois(discoverId, PENNSIEVE_API_LOCATION)) Promise.all(promiseList).then((values) => { resolve(values.flat()) }); }) } _processResultsForCards(results) { let newResults = [] let newResult = {} for (let res of results) { newResult = { ...res } newResult = { anatomy: res.anatomy ? res.anatomy.organ.map((organ => organ.curie)) : undefined, doi: res.item.curie.split(':')[1], name: res.item.name, description: res.item.description, updated: res.pennsieve ? res.pennsieve.updatedAt : undefined, publishDate: res.pennsieve ? res.pennsieve.publishDate : undefined, datasetId: res.objectID, detailsReady: false } newResults.push(newResult) } return newResults } _processAnatomy(hits) { let foundKeyWords = [] let foundLabels = [] let uniqueLabels = [] let uniqueKeywords = [] hits.forEach(hit => { if (hit.item && hit.item.keywords) { hit.item.keywords.forEach(keywordObj => { let keyword = keywordObj.keyword.toUpperCase() if (keyword.includes('UBERON') || keyword.includes('ILX')) { foundKeyWords.push(this._processUberonURL(keyword)) } }) } if (hit.anatomy && hit.anatomy.organ ) { hit.anatomy.organ.forEach(anatomy => { if (anatomy.curie) { foundKeyWords.push(anatomy.curie) foundLabels.push(anatomy.name) } }) } }) uniqueKeywords = [...new Set(foundKeyWords) ] uniqueLabels = [...new Set(foundLabels) ] this.anatomyFacetLabels = uniqueLabels return uniqueKeywords } _processUberonURL(url) { let ub = url.split('/').pop() return ub.replace('_', ':') } /** * Get Search results * This is using fetch from the Algolia API */ search(filter, query = '', hitsperPage = 10, page = 1) { return new Promise(resolve => { this.index .search(query, { facets: ['*'], hitsPerPage: hitsperPage, page: page - 1, filters: filter, attributesToHighlight: [], attributesToRetrieve: [ 'pennsieve.publishDate', 'pennsieve.updatedAt', 'item.curie', 'item.name', 'item.description', 'objectID', 'anatomy.organ.curie' ], }) .then(response => { let searchData = { items: this._processResultsForCards(response.hits), total: response.nbHits, discoverIds: response.hits.map(r => r.pennsieve ? r.pennsieve.identifier : r.objectID), dois: response.hits.map(r => r.item.curie.split(':')[1]) } resolve(searchData) }) }) } /** * Get key words * This is used to return all keywords for a given search. Note that you often want the hits per page to be maxed out */ anatomyInSearch(filter, query = '', hitsperPage = 999999, page = 1) { return new Promise(resolve => { this.index .search(query, { facets: ['*'], hitsPerPage: hitsperPage, page: page - 1, filters: filter, attributesToHighlight: [], attributesToRetrieve: [ 'objectID', 'item.keywords.keyword', 'anatomy.organ.name', 'anatomy.organ.curie', 'anatomy.organ.subsubcategory.name' ], }) .then(response => { // Saving the line below incase we want to starty using keywords again // let anatomyAsUberons = this._processAnatomy(response.hits) resolve({ forFlatmap: this.processResultsForFlatmap(response.facets ,response.hits), forScaffold: this.processResultsForScaffold(response.hits) }) }) }) } processResultsForFlatmap(facets, hits) { const filteredOrganNames = this.filterAvailableAnatomies(facets); let curieForDatasets = hits.map(h=>{ const data = { id: h.objectID, terms: h.anatomy ? h.anatomy.organ.map(o => { if (filteredOrganNames.includes(o.name.toLowerCase())) { return o.curie } }).filter(Boolean) : [] } return data }) return curieForDatasets } filterAvailableAnatomies(facets) { const anatomyOrganName = facets['anatomy.organ.name'] const anatomyOrganCategoryName = facets['anatomy.organ.category.name'] const anatomyOrganSubcategoryName = facets['anatomy.organ.subcategory.name'] const anatomyOrganSubsubcategoryName = facets['anatomy.organ.subsubcategory.name'] const anatomyOrganNames = anatomyOrganName ? Object.keys(anatomyOrganName) : [] const anatomyOrganCategoryNames = anatomyOrganCategoryName ? Object.keys(anatomyOrganCategoryName) : [] const anatomyOrganSubcategoryNames = anatomyOrganSubcategoryName ? Object.keys(anatomyOrganSubcategoryName) : [] const anatomyOrganSubsubcategoryNames = anatomyOrganSubsubcategoryName ? Object.keys(anatomyOrganSubsubcategoryName) : [] const filteredOrganNames = []; anatomyOrganCategoryNames.forEach((_categoryName) => { const categoryName = _categoryName.toLowerCase(); anatomyOrganNames.forEach((_organName) => { const organName = _organName.toLowerCase(); const fullName = `${categoryName}.${organName}` const foundNamesInSubsub = [] const found = anatomyOrganSubcategoryNames.some((_subcategoryName) => { const subcategoryName = _subcategoryName.toLowerCase(); if (subcategoryName === fullName) { const foundNameInSubsub = anatomyOrganSubsubcategoryNames.find((name) => name.toLocaleLowerCase().includes(subcategoryName)) if (foundNameInSubsub) { const subsubOrganName = foundNameInSubsub.replace(`${subcategoryName}.`, ''); if (anatomyOrganNames.map((name) => name.toLowerCase()).includes(subsubOrganName)) { foundNamesInSubsub.push(subsubOrganName) } } return true } }); if (foundNamesInSubsub.length) { filteredOrganNames.push(...foundNamesInSubsub.map(name => name.toLowerCase())) } if (found) { filteredOrganNames.push(organName); } }) }) return filteredOrganNames; } processResultsForScaffold(hits) { let numberOfDatasetsForAnatomy = {} hits.forEach(hit => { if (hit.anatomy && hit.anatomy.organ ) { hit.anatomy.organ.forEach(anatomy => { if (anatomy.name) { if (numberOfDatasetsForAnatomy[anatomy.name]) { numberOfDatasetsForAnatomy[anatomy.name]++ } else { numberOfDatasetsForAnatomy[anatomy.name] = 1 } } }) } }) return numberOfDatasetsForAnatomy } }