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) {
let error = null
if (e.response) {
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})`
if (e.response.status === 404) {
error = new DoesNotExist(errorMessage)
......@@ -136,7 +136,9 @@ function handleApiError(e) {
} else {
error = new Error(errorMessage)
}
console.log('### D', message, body)
error.status = e.response.status
error.apiMessage = message
} else {
if (e.message === 'Failed to fetch') {
error = new ApiError(e.message)
......
......@@ -2,7 +2,7 @@ import React, { useRef, useState, useContext, useCallback, useMemo } from 'react
import {searchContext} from './SearchContext'
import Autocomplete from '@material-ui/lab/Autocomplete'
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 { apiContext } from '../api'
......@@ -26,7 +26,7 @@ const Options = {
*/
export default function SearchBar() {
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(() => {
return Object.keys(searchQuantities)
.map(quantity => searchQuantities[quantity].name)
......@@ -37,13 +37,30 @@ export default function SearchBar() {
const [options, setOptions] = useState(defaultOptions)
const [loading, setLoading] = useState(false)
const [inputValue, setInputValue] = useState('')
const [searchType, setSearchType] = useState('nomad')
const {api} = useContext(apiContext)
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 = ''
if (pagination && statistics) {
if (error) {
helperText = '' + (error.apiMessage || error)
} else if (pagination && statistics) {
if (pagination.total === 0) {
helperText = <span>There are no more entries matching your criteria.</span>
} else {
......@@ -92,7 +109,7 @@ export default function SearchBar() {
setLoading(false)
})
}, 200)
}, [api, suggestionsTimerRef])
}, [api, suggestionsTimerRef, apiQuery])
const handleInputChange = useCallback((event, value, reason) => {
if (reason === 'input') {
......@@ -147,6 +164,29 @@ export default function SearchBar() {
}
}, [open])
const commonTextFieldProps = params => ({
error: !!error,
helperText: helperText,
variant: 'outlined',
fullWidth: true,
...params
})
const commonInputProps = (params) => ({
...params,
startAdornment: (
<React.Fragment>
<InputAdornment position="start">
<Tooltip title="Switch between NOMAD's quantity=value search and the Optimade filter language.">
<Button onClick={handleSearchTypeClicked}size="small">{searchType}</Button>
</Tooltip>
</InputAdornment>
{params.startAdornment}
</React.Fragment>
)
})
if (searchType === 'nomad') {
return <Autocomplete
multiple
freeSolo
......@@ -169,12 +209,10 @@ export default function SearchBar() {
filterOptions={filterOptions}
renderInput={(params) => (
<TextField
{...params}
helperText={helperText}
label='Search with quantity=value'
variant='outlined'
{...commonTextFieldProps(params)}
label={searchType === 'nomad' ? 'Search with quantity=value' : 'Search with Optimade filter language'}
InputProps={{
...params.InputProps,
...commonInputProps(params.InputProps),
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color='inherit' size={20} /> : null}
......@@ -185,4 +223,21 @@ export default function SearchBar() {
/>
)}
/>
} 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 = {
searchQuantities['from_time'] = {name: 'from_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.
*/
......@@ -144,12 +145,13 @@ export default function SearchContext({initialRequest, initialQuery, query, chil
metrics: (metric === domain.defaultSearchMetric) ? [] : [metric],
domain: domain.key
}
console.log('+++', requestRef.current.query)
const apiQuery = {
...apiRequest,
owner: owner,
...initialQuery,
...requestRef.current.query,
...query
query
}
if (dateHistogram) {
dateHistogramInterval = Dates.intervalSeconds(
......@@ -169,8 +171,11 @@ export default function SearchContext({initialRequest, initialQuery, query, chil
until_time: apiQuery.until_time
})
}).catch(error => {
setResponse({...emptyResponse, metric: metric})
setResponse({...emptyResponse, metric: metric, error: error})
console.log('***', error, error.status)
if (error.status !== 400) {
raiseError(error)
}
})
}, [requestRef, setResponse, api])
......
......@@ -2,8 +2,8 @@ import { createMuiTheme } from '@material-ui/core'
window.nomadEnv = window.nomadEnv || {}
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 = `${appBase}/api`
// export const apiBase = 'http://labdev-nomad.esc.rzg.mpg.de/fairdi/nomad/testing-major/api'
export const apiBase = `${appBase}/api`
export const optimadeBase = `${appBase}/optimade`
export const guiBase = process.env.PUBLIC_URL
export const matomoUrl = window.nomadEnv.matomoUrl
......
......@@ -192,8 +192,8 @@ def apply_search_parameters(search_request: search.SearchRequest, args: Dict[str
if optimade is not None:
q = filterparser.parse_filter(optimade)
search_request.query(q)
except filterparser.FilterException:
abort(400, 'could not parse optimade query')
except filterparser.FilterException as e:
abort(400, 'Could not parse optimade query: %s' % (str(e)))
# search parameter
search_request.search_parameters(**{
......
......@@ -18,30 +18,15 @@ from elasticsearch_dsl import Q
from optimade.filterparser import LarkParser
from optimade.filtertransformers.elasticsearch import Transformer, Quantity
from nomad.datamodel import OptimadeEntry
class FilterException(Exception):
''' Raised on parsing a filter expression with syntactic of semantic errors. '''
pass
quantities: Dict[str, Quantity] = {
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']
_quantities: Dict[str, Quantity] = None
_parser = LarkParser(version=(0, 10, 0))
_transformer = Transformer(quantities=quantities.values())
_transformer = None
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
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:
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