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: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ GEM
puma (5.6.9)
nio4r (~> 2.0)
racc (1.8.1)
rack (2.2.19)
rack (2.2.20)
rack-brotli (1.1.0)
brotli (>= 0.1.7)
rack (>= 1.4)
Expand Down
6 changes: 4 additions & 2 deletions app/controllers/api/v1/bulk_download_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ def summary
end

@study_file_info = ::BulkDownloadService.get_download_info(valid_accessions)
hca_file_info = ::AzulSearchService.get_file_summary_info(hca_accessions)
@study_file_info += hca_file_info if hca_file_info.any?
if hca_accessions.present?
hca_file_info = ::AzulSearchService.get_file_summary_info(hca_accessions)
@study_file_info += hca_file_info if hca_file_info.any?
end

render json: @study_file_info
end
Expand Down
91 changes: 66 additions & 25 deletions app/javascript/components/search/controls/OptionsButton.jsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,87 @@
import React, { useState, useContext } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCogs } from '@fortawesome/free-solid-svg-icons'
import { Popover, OverlayTrigger } from 'react-bootstrap'
import { faCogs, faTimesCircle } from '@fortawesome/free-solid-svg-icons'
import { StudySearchContext } from '~/providers/StudySearchProvider'

import OptionsControl from '~/components/search/controls/OptionsControl'
import useCloseableModal from '~/hooks/closeableModal'

/** list of configured search option entries */
export const configuredOptions = [
{ searchProp: 'external', value: 'hca', label: 'Include HCA results' },
{ searchProp: 'data_types', value: 'raw_counts', label: 'Has raw counts', multiple: true },
{ searchProp: 'data_types', value: 'diff_exp', label: 'Has differential expression', multiple: true },
{ searchProp: 'data_types', value: 'spatial', label: 'Has spatial data', multiple: true }
]

/** Search options button for filtering results by data types/sources */
export default function OptionsButton() {
const searchContext = useContext(StudySearchContext)
const [showOptions, setShowOptions] = useState(false)
const configuredOptions = [
{ searchProp: 'external', value: 'hca', label: 'Include HCA results' },
{ searchProp: 'data_types', value: 'raw_counts', label: 'Has raw counts', multiple: true },
{ searchProp: 'data_types', value: 'diff_exp', label: 'Has differential expression', multiple: true },
{ searchProp: 'data_types', value: 'spatial', label: 'Has spatial data', multiple: true }
]

const optionsPopover = <Popover data-analytics-name='search-options-menu' id='search-options-menu'>
<ul className="facet-filter-list">

/** determine if any options have been selected */
function searchOptionSelected() {
const opts = []
configuredOptions.map(option => {
const opt = option.searchProp
if (searchContext.params[opt] && searchContext.params[opt].length > 0) {
opts.push(opt)
}
})
return opts.length > 0
}

/** clear all selected options */
function clearAllOptions() {
const existingParams = searchContext.params
configuredOptions.map(option => {delete existingParams[option.searchProp]})
searchContext.updateSearch(existingParams)
setShowOptions(false)
}

const { node, clearNode, handleButtonClick } = useCloseableModal(showOptions, setShowOptions)

const optionsMenu = <div data-analytics-name='search-options-menu' id='search-options-menu'>
<ul>
{
configuredOptions.map((option, index) => {
return <OptionsControl
key={`${option.searchProp}-${index}`}
searchContext={searchContext}
searchProp={option.searchProp}
value={option.value}
label={option.label}
multiple={option.multiple}
/>
return <OptionsControl
key={`${option.searchProp}-${index}`}
searchContext={searchContext}
searchProp={option.searchProp}
value={option.value}
label={option.label}
multiple={option.multiple}
/>
})
}
</ul>
</Popover>
{ showOptions && searchOptionSelected() &&
<a className='pull-right' onClick={clearAllOptions}>
Clear&nbsp;
<span
ref={clearNode}
data-testid='clear-search-options'
onClick={clearAllOptions}
aria-label='Clear options'
>
<FontAwesomeIcon icon={faTimesCircle}/>
</span>
</a>
}
</div>

return (
<OverlayTrigger trigger={['click']} placement='bottom' animation={false} overlay={optionsPopover}>
<span id="search-options-button" data-testid="search-options-button"
className={`facet ${showOptions ? 'active' : ''}`}>
<a onClick={() => setShowOptions(!showOptions)}>
<span
ref={node}
id="search-options-button"
data-testid="search-options-button"
className={`facet ${showOptions ? 'active' : ''}`}
>
<a onClick={handleButtonClick}>
<FontAwesomeIcon className="icon-left" icon={faCogs}/>Options
</a>
{ showOptions && optionsMenu }
</span>
</OverlayTrigger>
)
}
18 changes: 5 additions & 13 deletions app/javascript/components/search/controls/OptionsControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useState } from 'react'
export default function OptionsControl({ searchContext, searchProp, value, label, multiple = false }) {
const defaultChecked = isDefaultChecked()
const [isChecked, setIsChecked] = useState(defaultChecked)
const optionId = `options-control-${searchProp}-${value}`

/** return existing url query params for this option */
function getExistingOpts() {
Expand All @@ -12,11 +13,7 @@ export default function OptionsControl({ searchContext, searchProp, value, label

/** set the default state for this option checkbox */
function isDefaultChecked() {
if (multiple) {
return getExistingOpts().includes(value)
} else {
return searchContext.params[searchProp] === value
}
return multiple ? getExistingOpts().includes(value) : searchContext.params[searchProp] === value
}

/** toggle state of checkbox */
Expand All @@ -32,14 +29,9 @@ export default function OptionsControl({ searchContext, searchProp, value, label
}

return (
<li id={`options-control-${searchProp}`} key={`options-control-${searchProp}`}>
<label>
<input data-testid={`options-checkbox-${searchProp}-${value}`}
type="checkbox"
checked={isChecked}
onChange={() => {toggleCheckbox(!isChecked)}}/>
<span onClick={() => {toggleCheckbox(!isChecked)}} >{ label }</span>
</label>
<li id={optionId} key={optionId} onClick={() => {toggleCheckbox(!isChecked)}}>
<input data-testid={`options-checkbox-${searchProp}-${value}`} type="checkbox" checked={isChecked} readOnly />
{ label }
</li>
)
}
44 changes: 22 additions & 22 deletions app/javascript/styles/_search.scss
Original file line number Diff line number Diff line change
Expand Up @@ -396,33 +396,33 @@ span.text-search {
}

#search-options-menu {
border: 1px solid #aaa;
background: #fff;
position: absolute;
z-index: 9000;
border-radius: 5px;
padding-top: 10px;
padding-right: 5px;
margin-top: 2px;

ul {
list-style-type: none;
padding-left: 0px;
}

ul.facet-filter-list {
max-height: 300px;
overflow-y: auto;
margin-bottom: 0;
label {
display: inline-block;
white-space: nowrap;
}
input {
font-size: 2em;
vertical-align: middle;
margin-top: 0px;
margin-right: 12px;
}
label span {

li {
padding: 0 0.8em;
margin-bottom: 0.8em;
cursor: pointer;
vertical-align: middle;
font-weight: normal;
}
}

li {
width: 100%;
margin-bottom: 0.5em;
input {
font-size: 2em;
vertical-align: top;
margin-top: 4px;
margin-right: 12px;
cursor: pointer;
}
}
}
}
Loading
Loading