Commit a420d4bc authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Fixed optimade in repo search. Added optimade to search bar. #327

parent 0c926116
Pipeline #74214 failed with stages
in 37 minutes and 5 seconds
...@@ -125,7 +125,7 @@ function handleApiError(e) { ...@@ -125,7 +125,7 @@ function handleApiError(e) {
let error = null let error = null
if (e.response) { if (e.response) {
const body = e.response.body const body = e.response.body
const message = (body && body.message) ? body.message : e.response.statusText const message = (body && (body.description || body.message)) || e.response.statusText
const errorMessage = `${message} (${e.response.status})` const errorMessage = `${message} (${e.response.status})`
if (e.response.status === 404) { if (e.response.status === 404) {
error = new DoesNotExist(errorMessage) error = new DoesNotExist(errorMessage)
...@@ -136,7 +136,9 @@ function handleApiError(e) { ...@@ -136,7 +136,9 @@ function handleApiError(e) {
} else { } else {
error = new Error(errorMessage) error = new Error(errorMessage)
} }
console.log('### D', message, body)
error.status = e.response.status error.status = e.response.status
error.apiMessage = message
} else { } else {
if (e.message === 'Failed to fetch') { if (e.message === 'Failed to fetch') {
error = new ApiError(e.message) error = new ApiError(e.message)
......
...@@ -2,7 +2,7 @@ import React, { useRef, useState, useContext, useCallback, useMemo } from 'react ...@@ -2,7 +2,7 @@ import React, { useRef, useState, useContext, useCallback, useMemo } from 'react
import {searchContext} from './SearchContext' import {searchContext} from './SearchContext'
import Autocomplete from '@material-ui/lab/Autocomplete' import Autocomplete from '@material-ui/lab/Autocomplete'
import TextField from '@material-ui/core/TextField' import TextField from '@material-ui/core/TextField'
import { CircularProgress } from '@material-ui/core' import { CircularProgress, InputAdornment, Typography, Button, Tooltip } from '@material-ui/core'
import * as searchQuantities from '../../searchQuantities.json' import * as searchQuantities from '../../searchQuantities.json'
import { apiContext } from '../api' import { apiContext } from '../api'
...@@ -26,7 +26,7 @@ const Options = { ...@@ -26,7 +26,7 @@ const Options = {
*/ */
export default function SearchBar() { export default function SearchBar() {
const suggestionsTimerRef = useRef(null) const suggestionsTimerRef = useRef(null)
const {response: {statistics, pagination}, domain, query, apiQuery, setQuery} = useContext(searchContext) const {response: {statistics, pagination, error}, domain, query, apiQuery, setQuery} = useContext(searchContext)
const defaultOptions = useMemo(() => { const defaultOptions = useMemo(() => {
return Object.keys(searchQuantities) return Object.keys(searchQuantities)
.map(quantity => searchQuantities[quantity].name) .map(quantity => searchQuantities[quantity].name)
...@@ -37,13 +37,30 @@ export default function SearchBar() { ...@@ -37,13 +37,30 @@ export default function SearchBar() {
const [options, setOptions] = useState(defaultOptions) const [options, setOptions] = useState(defaultOptions)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [inputValue, setInputValue] = useState('') const [inputValue, setInputValue] = useState('')
const [searchType, setSearchType] = useState('nomad')
const {api} = useContext(apiContext) const {api} = useContext(apiContext)
const autocompleteValue = Object.keys(query).map(quantity => Options.join(quantity, query[quantity])) const autocompleteValue = Object.keys(query).map(quantity => Options.join(quantity, query[quantity]))
const handleSearchTypeClicked = useCallback(() => {
if (searchType === 'nomad') {
setSearchType('optimade')
// handleChange(null, [])
} else {
setSearchType('nomad')
// handleChange(null, [])
}
}, [searchType, setSearchType])
const handleOptimadeEntered = useCallback(query => {
setQuery({'dft.optimade': query})
})
let helperText = '' let helperText = ''
if (pagination && statistics) { if (error) {
helperText = '' + (error.apiMessage || error)
} else if (pagination && statistics) {
if (pagination.total === 0) { if (pagination.total === 0) {
helperText = <span>There are no more entries matching your criteria.</span> helperText = <span>There are no more entries matching your criteria.</span>
} else { } else {
...@@ -92,7 +109,7 @@ export default function SearchBar() { ...@@ -92,7 +109,7 @@ export default function SearchBar() {
setLoading(false) setLoading(false)
}) })
}, 200) }, 200)
}, [api, suggestionsTimerRef]) }, [api, suggestionsTimerRef, apiQuery])
const handleInputChange = useCallback((event, value, reason) => { const handleInputChange = useCallback((event, value, reason) => {
if (reason === 'input') { if (reason === 'input') {
...@@ -147,42 +164,80 @@ export default function SearchBar() { ...@@ -147,42 +164,80 @@ export default function SearchBar() {
} }
}, [open]) }, [open])
return <Autocomplete const commonTextFieldProps = params => ({
multiple error: !!error,
freeSolo helperText: helperText,
inputValue={inputValue} variant: 'outlined',
value={autocompleteValue} fullWidth: true,
limitTags={4} ...params
id='search-bar' })
open={open}
onOpen={() => { const commonInputProps = (params) => ({
setOpen(true) ...params,
}} startAdornment: (
onClose={() => { <React.Fragment>
setOpen(false) <InputAdornment position="start">
}} <Tooltip title="Switch between NOMAD's quantity=value search and the Optimade filter language.">
onChange={handleChange} <Button onClick={handleSearchTypeClicked}size="small">{searchType}</Button>
onInputChange={handleInputChange} </Tooltip>
getOptionSelected={(option, value) => option === value} </InputAdornment>
options={options} {params.startAdornment}
loading={loading} </React.Fragment>
filterOptions={filterOptions} )
renderInput={(params) => ( })
<TextField
{...params} if (searchType === 'nomad') {
helperText={helperText} return <Autocomplete
label='Search with quantity=value' multiple
variant='outlined' freeSolo
InputProps={{ inputValue={inputValue}
...params.InputProps, value={autocompleteValue}
endAdornment: ( limitTags={4}
<React.Fragment> id='search-bar'
{loading ? <CircularProgress color='inherit' size={20} /> : null} open={open}
{params.InputProps.endAdornment} onOpen={() => {
</React.Fragment> setOpen(true)
) }}
}} onClose={() => {
/> setOpen(false)
)} }}
/> onChange={handleChange}
onInputChange={handleInputChange}
getOptionSelected={(option, value) => option === value}
options={options}
loading={loading}
filterOptions={filterOptions}
renderInput={(params) => (
<TextField
{...commonTextFieldProps(params)}
label={searchType === 'nomad' ? 'Search with quantity=value' : 'Search with Optimade filter language'}
InputProps={{
...commonInputProps(params.InputProps),
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color='inherit' size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
)}
/>
} else {
return <TextField
{...commonTextFieldProps({})}
label={searchType === 'nomad' ? 'Search with quantity=value' : 'Search with Optimade filter language'}
InputProps={{
...commonInputProps({})
}}
defaultValue={query['dft.optimade'] || ''}
onKeyPress={(ev) => {
console.log(`Pressed keyCode ${ev.key}`)
if (ev.key === 'Enter') {
handleOptimadeEntered(ev.target.value)
ev.preventDefault()
}
}}
/>
}
} }
...@@ -27,6 +27,7 @@ export const Dates = { ...@@ -27,6 +27,7 @@ export const Dates = {
searchQuantities['from_time'] = {name: 'from_time'} searchQuantities['from_time'] = {name: 'from_time'}
searchQuantities['until_time'] = {name: 'until_time'} searchQuantities['until_time'] = {name: 'until_time'}
searchQuantities['dft.optimade'] = {name: 'dft.optimade'}
/** /**
* A custom hook that reads and writes search parameters from the current URL. * A custom hook that reads and writes search parameters from the current URL.
*/ */
...@@ -144,12 +145,13 @@ export default function SearchContext({initialRequest, initialQuery, query, chil ...@@ -144,12 +145,13 @@ export default function SearchContext({initialRequest, initialQuery, query, chil
metrics: (metric === domain.defaultSearchMetric) ? [] : [metric], metrics: (metric === domain.defaultSearchMetric) ? [] : [metric],
domain: domain.key domain: domain.key
} }
console.log('+++', requestRef.current.query)
const apiQuery = { const apiQuery = {
...apiRequest, ...apiRequest,
owner: owner, owner: owner,
...initialQuery, ...initialQuery,
...requestRef.current.query, ...requestRef.current.query,
...query query
} }
if (dateHistogram) { if (dateHistogram) {
dateHistogramInterval = Dates.intervalSeconds( dateHistogramInterval = Dates.intervalSeconds(
...@@ -169,8 +171,11 @@ export default function SearchContext({initialRequest, initialQuery, query, chil ...@@ -169,8 +171,11 @@ export default function SearchContext({initialRequest, initialQuery, query, chil
until_time: apiQuery.until_time until_time: apiQuery.until_time
}) })
}).catch(error => { }).catch(error => {
setResponse({...emptyResponse, metric: metric}) setResponse({...emptyResponse, metric: metric, error: error})
raiseError(error) console.log('***', error, error.status)
if (error.status !== 400) {
raiseError(error)
}
}) })
}, [requestRef, setResponse, api]) }, [requestRef, setResponse, api])
......
...@@ -2,8 +2,8 @@ import { createMuiTheme } from '@material-ui/core' ...@@ -2,8 +2,8 @@ import { createMuiTheme } from '@material-ui/core'
window.nomadEnv = window.nomadEnv || {} window.nomadEnv = window.nomadEnv || {}
export const appBase = window.nomadEnv.appBase.replace(/\/$/, '') export const appBase = window.nomadEnv.appBase.replace(/\/$/, '')
export const apiBase = 'http://labdev-nomad.esc.rzg.mpg.de/fairdi/nomad/testing-major/api' // export const apiBase = 'http://labdev-nomad.esc.rzg.mpg.de/fairdi/nomad/testing-major/api'
// export const apiBase = `${appBase}/api` export const apiBase = `${appBase}/api`
export const optimadeBase = `${appBase}/optimade` export const optimadeBase = `${appBase}/optimade`
export const guiBase = process.env.PUBLIC_URL export const guiBase = process.env.PUBLIC_URL
export const matomoUrl = window.nomadEnv.matomoUrl export const matomoUrl = window.nomadEnv.matomoUrl
......
...@@ -192,8 +192,8 @@ def apply_search_parameters(search_request: search.SearchRequest, args: Dict[str ...@@ -192,8 +192,8 @@ def apply_search_parameters(search_request: search.SearchRequest, args: Dict[str
if optimade is not None: if optimade is not None:
q = filterparser.parse_filter(optimade) q = filterparser.parse_filter(optimade)
search_request.query(q) search_request.query(q)
except filterparser.FilterException: except filterparser.FilterException as e:
abort(400, 'could not parse optimade query') abort(400, 'Could not parse optimade query: %s' % (str(e)))
# search parameter # search parameter
search_request.search_parameters(**{ search_request.search_parameters(**{
......
...@@ -18,30 +18,15 @@ from elasticsearch_dsl import Q ...@@ -18,30 +18,15 @@ from elasticsearch_dsl import Q
from optimade.filterparser import LarkParser from optimade.filterparser import LarkParser
from optimade.filtertransformers.elasticsearch import Transformer, Quantity from optimade.filtertransformers.elasticsearch import Transformer, Quantity
from nomad.datamodel import OptimadeEntry
class FilterException(Exception): class FilterException(Exception):
''' Raised on parsing a filter expression with syntactic of semantic errors. ''' ''' Raised on parsing a filter expression with syntactic of semantic errors. '''
pass pass
quantities: Dict[str, Quantity] = { _quantities: Dict[str, Quantity] = None
q.name: Quantity(
q.name, es_field='dft.optimade.%s' % q.name,
elastic_mapping_type=q.a_search.mapping.__class__)
for q in OptimadeEntry.m_def.all_quantities.values()
if 'search' in q.m_annotations}
quantities['elements'].length_quantity = quantities['nelements']
quantities['dimension_types'].length_quantity = quantities['dimension_types']
quantities['elements'].has_only_quantity = Quantity(name='only_atoms')
quantities['elements'].nested_quantity = quantities['elements_ratios']
quantities['elements_ratios'].nested_quantity = quantities['elements_ratios']
_parser = LarkParser(version=(0, 10, 0)) _parser = LarkParser(version=(0, 10, 0))
_transformer = Transformer(quantities=quantities.values()) _transformer = None
def parse_filter(filter_str: str) -> Q: def parse_filter(filter_str: str) -> Q:
...@@ -54,6 +39,25 @@ def parse_filter(filter_str: str) -> Q: ...@@ -54,6 +39,25 @@ def parse_filter(filter_str: str) -> Q:
FilterException: If the given str cannot be parsed, or if there are any semantic FilterException: If the given str cannot be parsed, or if there are any semantic
errors in the given expression. errors in the given expression.
''' '''
global _quantities
global _transformer
if _quantities is None:
from nomad.datamodel import OptimadeEntry
_quantities = {
q.name: Quantity(
q.name, es_field='dft.optimade.%s' % q.name,
elastic_mapping_type=q.a_search.mapping.__class__)
for q in OptimadeEntry.m_def.all_quantities.values()
if 'search' in q.m_annotations}
_quantities['elements'].length_quantity = _quantities['nelements']
_quantities['dimension_types'].length_quantity = _quantities['dimension_types']
_quantities['elements'].has_only_quantity = Quantity(name='only_atoms')
_quantities['elements'].nested_quantity = _quantities['elements_ratios']
_quantities['elements_ratios'].nested_quantity = _quantities['elements_ratios']
_transformer = Transformer(quantities=_quantities.values())
try: try:
parse_tree = _parser.parse(filter_str) parse_tree = _parser.parse(filter_str)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment