From 8e22a5cc4ee6a3fc110d892065377dc2af6c3563 Mon Sep 17 00:00:00 2001 From: Pedro Assis Date: Fri, 25 Jun 2021 13:49:39 -1000 Subject: [PATCH 1/2] redirecting region search to data services --- src/encoded/genomic_data_service.py | 18 ++++++++++++++++++ src/encoded/region_search.py | 27 ++++++++++++--------------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/encoded/genomic_data_service.py b/src/encoded/genomic_data_service.py index 4f8c39b50b7..1f1b7bd25eb 100644 --- a/src/encoded/genomic_data_service.py +++ b/src/encoded/genomic_data_service.py @@ -14,6 +14,7 @@ ] RNA_GET_EXPRESSIONS = '/expressions/bytes' RNA_GET_AUTOCOMPLETE = '/autocomplete' +REGION_SEARCH = '/region-search' # react component orders columns by "the position" in the hash map RNA_GET_COLUMNS = { @@ -262,3 +263,20 @@ def rna_get(self): } return response + + + def region_search(self, assembly, chromosome, start, end): + params = { + 'assembly': assembly, + 'chr': chromosome.replace('chr', ''), + 'start': start, + 'end': end + } + + query_params = '&'.join([f'{k}={params[k]}' for k in params.keys()]) + + url = f'{self.path}{REGION_SEARCH}?{query_params}' + + results = requests.get(url, timeout=3).json() + + return results diff --git a/src/encoded/region_search.py b/src/encoded/region_search.py index 766fbd5a4c0..aabe3883174 100644 --- a/src/encoded/region_search.py +++ b/src/encoded/region_search.py @@ -8,6 +8,8 @@ import requests from urllib.parse import urlencode +from encoded.genomic_data_service import GenomicDataService + import logging import re @@ -253,6 +255,9 @@ def region_search(context, request): """ Search files by region. """ + + data_service = GenomicDataService(context.registry, request) + types = request.registry[TYPES] result = { '@id': '/region-search/' + ('?' + request.query_string.split('&referrer')[0] if request.query_string else ''), @@ -314,25 +319,17 @@ def region_search(context, request): chr=chromosome, start=start, end=end ) - # Search for peaks for the coordinates we got try: - # including inner hits is very slow - # figure out how to distinguish browser requests from .embed method requests - if 'peak_metadata' in request.query_string: - peak_query = get_peak_query(start, end, with_inner_hits=True, within_peaks=region_inside_peak_status) - else: - peak_query = get_peak_query(start, end, within_peaks=region_inside_peak_status) - peak_results = snp_es.search(body=peak_query, - index=chromosome.lower(), - doc_type=_GENOME_TO_ALIAS[assembly], - size=99999) + peak_results = data_service.region_search(_GENOME_TO_ALIAS[assembly], chromosome.lower(), start, end) except Exception: result['notification'] = 'Error during search' return result + file_uuids = [] - for hit in peak_results['hits']['hits']: - if hit['_id'] not in file_uuids: - file_uuids.append(hit['_id']) + for hit in peak_results['regions']: + region_uuid = hit['file_url'].split('/')[-1] + if region_uuid not in file_uuids: + file_uuids.append(region_uuid) file_uuids = list(set(file_uuids)) result['notification'] = 'No results found' @@ -363,7 +360,7 @@ def region_search(context, request): result['@graph'] = list(format_results(request, es_results['hits']['hits'])) result['total'] = total = es_results['hits']['total'] result['facets'] = format_facets(es_results, _FACETS, used_filters, schemas, total, principals) - result['peaks'] = list(peak_results['hits']['hits']) + result['peaks'] = list(peak_results['regions']) result['download_elements'] = get_peak_metadata_links(request) if result['total'] > 0: result['notification'] = 'Success' From fa48718d22f229f45dd9f321f389d1225d1c0462 Mon Sep 17 00:00:00 2001 From: Pedro Assis Date: Mon, 26 Jul 2021 17:11:53 -0400 Subject: [PATCH 2/2] for demo only --- src/encoded/region_search.py | 7 + .../static/components/region_search.js | 208 +++++++++++++----- .../static/scss/encoded/modules/_search.scss | 1 + 3 files changed, 156 insertions(+), 60 deletions(-) diff --git a/src/encoded/region_search.py b/src/encoded/region_search.py index aabe3883174..40e94b12466 100644 --- a/src/encoded/region_search.py +++ b/src/encoded/region_search.py @@ -354,9 +354,16 @@ def region_search(context, request): used_filters['files.uuid'] = file_uuids query['aggs'] = set_facets(_FACETS, used_filters, principals, ['Experiment']) schemas = (types[item_type].schema for item_type in ['Experiment']) + + ######################### DO NOT MERGE THIS + from elasticsearch import Elasticsearch + es = Elasticsearch(hosts=['34.208.231.246'], port=9201) ### IP FROM ENCODE DEMO + ########################################### + es_results = es.search( body=query, index='experiment', doc_type='experiment', size=size, request_timeout=60 ) + result['@graph'] = list(format_results(request, es_results['hits']['hits'])) result['total'] = total = es_results['hits']['total'] result['facets'] = format_facets(es_results, _FACETS, used_filters, schemas, total, principals) diff --git a/src/encoded/static/components/region_search.js b/src/encoded/static/components/region_search.js index 6b32a8f3413..64994d64a38 100644 --- a/src/encoded/static/components/region_search.js +++ b/src/encoded/static/components/region_search.js @@ -1,12 +1,19 @@ import React from 'react'; import PropTypes from 'prop-types'; import url from 'url'; -import { Panel, PanelBody } from '../libs/ui/panel'; +import { Panel, PanelBody, TabPanel } from '../libs/ui/panel'; +import GenomeBrowser from './genome_browser'; +import * as globals from './globals'; +import getSeriesData from './series_search.js'; import { FacetList, Listing } from './search'; +import { ASSEMBLY_DETAILS, BrowserSelector } from './vis_defines'; +import { + SearchBatchDownloadController, + BatchDownloadActuator, +} from './batch_download'; +import { svgIcon } from '../libs/svg-icons'; +import QueryString from '../libs/query_string'; import { FetchedData, Param } from './fetched'; -import * as globals from './globals'; -import { BrowserSelector } from './vis_defines'; - const regionGenomes = [ { value: 'GRCh37', display: 'hg19' }, @@ -15,7 +22,6 @@ const regionGenomes = [ { value: 'GRCm38', display: 'mm10' }, ]; - const AutocompleteBox = (props) => { const terms = props.auto['@graph']; // List of matching terms from server const { handleClick } = props; @@ -195,15 +201,15 @@ class AdvSearch extends React.Component { } render() { - const { context } = this.props; + const context = this.props; const id = url.parse(this.context.location_href, true); const region = id.query.region || ''; if (this.state.genome === '') { let assembly = regionGenomes[0].value; - if (context.assembly) { + if (this.props.assembly) { assembly = regionGenomes.find((el) => ( - context.assembly === el.value || context.assembly === el.display + this.props.assembly === el.value || this.props.assembly === el.display )).value; } this.setState({ genome: assembly }); @@ -256,20 +262,60 @@ AdvSearch.contextTypes = { location_href: PropTypes.string, }; +// Default assembly for each organism +const defaultAssemblyByOrganism = { + 'Homo sapiens': 'GRCh38', + 'Mus musculus': 'mm10', +}; -// Maximum number of selected items that can be visualized. -const VISUALIZE_LIMIT = 100; - - -class RegionSearch extends React.Component { - constructor() { - super(); - - // Bind this to non-React methods. - this.onFilter = this.onFilter.bind(this); - } - - onFilter(e) { +// The encyclopedia page displays a table of results corresponding to a selected annotation type +const Encyclopedia = (props, context) => { + const defaultAssembly = 'GRCh38'; + const defaultFileDownload = 'all'; + const defaultVisualization = 'List View'; + const visualizationOptions = ['List View', 'Genome Browser']; + const encyclopediaVersion = 'ENCODE v5'; + const searchBase = url.parse(context.location_href).search || ''; + + const [selectedVisualization, setSelectedVisualization] = React.useState(defaultVisualization); + const [selectedAssembly, setAssembly] = React.useState(defaultAssembly); + const [assemblyList, setAssemblyList] = React.useState([defaultAssembly]); + + var browser_files = []; + + const results = props.context['@graph']; + results.forEach((res) => { + res['files'].forEach((file) => { + if (file['preferred_default'] && (file['file_format'] == 'bigBed' || file['file_format'] == 'bigWig')) { + browser_files.push(file); + } + }); + }); + + // Data which populates the browser + const [vizFiles, setVizFiles] = React.useState(browser_files); + + // vizFilesError is true when there are no results for selected filters + const [vizFilesError, setVizFilesError] = React.useState(false); + // Number of files available is displayed + const [totalFiles, setTotalFiles] = React.useState(browser_files.length); + + // Update assembly, organism, browser files, and download link when user clicks on tab + const handleTabClick = (tab) => { + setVizFiles([]); + setAssembly(defaultAssemblyByOrganism[tab]); + }; + + const { columns, notification, filters, facets, total } = props.context; +// const results = props.context['@graph']; + const trimmedSearchBase = searchBase.replace(/[?|&]limit=all/, ''); + + + // Maximum number of selected items that can be visualized. + const VISUALIZE_LIMIT = 100; + const visualizeDisabledTitle = total > VISUALIZE_LIMIT ? `Filter to ${VISUALIZE_LIMIT} to visualize` : ''; + + const onFilter = (e) => { if (this.props.onChange) { const search = e.currentTarget.getAttribute('href'); this.props.onChange(search); @@ -278,30 +324,25 @@ class RegionSearch extends React.Component { } } - render() { - const { context } = this.props; - const results = context['@graph']; - const { columns, notification, filters, facets, total } = context; - const searchBase = url.parse(this.context.location_href).search || ''; - const trimmedSearchBase = searchBase.replace(/[?|&]limit=all/, ''); - const visualizeDisabledTitle = total > VISUALIZE_LIMIT ? `Filter to ${VISUALIZE_LIMIT} to visualize` : ''; + const visualizationTabs = {}; + visualizationOptions.forEach((visualizationName) => { + visualizationTabs[visualizationName] =
{visualizationName}
; + }); - return ( -
-

Region search

- - {notification === 'Success' ? - - + + const listView = ( + +
-
+
+

Showing {results.length} of {total} @@ -312,7 +353,7 @@ class RegionSearch extends React.Component { rel="nofollow" className="btn btn-info btn-sm" href={searchBase ? `${searchBase}&limit=all` : '?limit=all'} - onClick={this.onFilter} + onClick={onFilter} > View All @@ -322,44 +363,91 @@ class RegionSearch extends React.Component { View 25 : null} } - -

- -
+
    {results.map((result) => Listing({ context: result, columns, key: result['@id'] }))}
-
+
- : null} -
- ); - } -} + ); -RegionSearch.propTypes = { - context: PropTypes.object.isRequired, - onChange: PropTypes.func, -}; + const genomeBrowserView = ( +
+
+ + +
+
+ +
-RegionSearch.defaultProps = { - onChange: null, +
+ +
+
+
+
+
+
+ ); + + return ( +
+
+
+
+
+

Region Search

+ {encyclopediaVersion} +
+
+ + + +
+ setSelectedVisualization(tab)} + tabCss="tab-button" + tabPanelCss="tab-container encyclopedia-tabs" + > + { selectedVisualization == 'List View' ? listView : genomeBrowserView } + +
+
+
+
+ ); }; -RegionSearch.contextTypes = { +Encyclopedia.contextTypes = { location_href: PropTypes.string, + navigate: PropTypes.func, + fetch: PropTypes.func, }; -export default AutocompleteBox; - -globals.contentViews.register(RegionSearch, 'region-search'); +globals.contentViews.register(Encyclopedia, 'region-search'); diff --git a/src/encoded/static/scss/encoded/modules/_search.scss b/src/encoded/static/scss/encoded/modules/_search.scss index 85ab2fa15e4..20b4b56a81b 100644 --- a/src/encoded/static/scss/encoded/modules/_search.scss +++ b/src/encoded/static/scss/encoded/modules/_search.scss @@ -35,6 +35,7 @@ $form-bg: #f3f3f3; display: flex; flex-direction: column; } + } @at-root #{&}__report-list {