Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
nomad-FAIR
Commits
05b81af7
Commit
05b81af7
authored
Apr 29, 2020
by
Markus Scheidgen
Browse files
Added real suggestions to searchbar.
parent
37b771aa
Changes
6
Hide whitespace changes
Inline
Side-by-side
gui/src/components/api.js
View file @
05b81af7
...
@@ -474,13 +474,14 @@ class Api {
...
@@ -474,13 +474,14 @@ class Api {
.
finally
(
this
.
onFinishLoading
)
.
finally
(
this
.
onFinishLoading
)
}
}
async
quantity
_search
(
quantity
,
search
,
size
,
noLoadingIndicator
)
{
async
suggestions
_search
(
quantity
,
search
,
include
,
size
,
noLoadingIndicator
)
{
if
(
!
noLoadingIndicator
)
{
if
(
!
noLoadingIndicator
)
{
this
.
onStartLoading
()
this
.
onStartLoading
()
}
}
return
this
.
swagger
()
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
repo
.
quantity
_search
({
.
then
(
client
=>
client
.
apis
.
repo
.
suggestions
_search
({
size
:
size
||
20
,
size
:
size
||
20
,
include
:
include
,
quantity
:
quantity
,
quantity
:
quantity
,
...
search
...
search
}))
}))
...
...
gui/src/components/search/SearchBarNew.js
View file @
05b81af7
import
React
from
'
react
'
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
'
...
@@ -6,15 +6,21 @@ import { CircularProgress } from '@material-ui/core'
...
@@ -6,15 +6,21 @@ import { CircularProgress } from '@material-ui/core'
import
*
as
searchQuantities
from
'
../../searchQuantities.json
'
import
*
as
searchQuantities
from
'
../../searchQuantities.json
'
import
{
apiContext
}
from
'
../api
'
import
{
apiContext
}
from
'
../api
'
const
defaultOptions
=
Object
.
keys
(
searchQuantities
).
map
(
quantity
=>
searchQuantities
[
quantity
].
name
)
export
default
function
SearchBar
()
{
export
default
function
SearchBar
()
{
const
[
open
,
setOpen
]
=
React
.
useState
(
false
)
const
suggestionsTimerRef
=
useRef
(
null
)
const
[
options
,
setOptions
]
=
React
.
useState
(
defaultOptions
)
const
{
response
:
{
statistics
,
pagination
},
domain
,
query
,
setQuery
}
=
useContext
(
searchContext
)
const
[
loading
,
setLoading
]
=
React
.
useState
(
false
)
const
defaultOptions
=
useMemo
(()
=>
{
const
[
inputValue
,
setInputValue
]
=
React
.
useState
(
''
)
return
Object
.
keys
(
searchQuantities
)
const
{
response
:
{
statistics
,
pagination
},
domain
,
query
,
setQuery
}
=
React
.
useContext
(
searchContext
)
.
map
(
quantity
=>
searchQuantities
[
quantity
].
name
)
const
{
api
}
=
React
.
useContext
(
apiContext
)
.
filter
(
quantity
=>
!
quantity
.
includes
(
'
.
'
)
||
quantity
.
startsWith
(
domain
.
key
+
'
.
'
))
},
[
domain
.
key
])
const
[
open
,
setOpen
]
=
useState
(
false
)
const
[
options
,
setOptions
]
=
useState
(
defaultOptions
)
const
[
loading
,
setLoading
]
=
useState
(
false
)
const
[
inputValue
,
setInputValue
]
=
useState
(
''
)
const
{
api
}
=
useContext
(
apiContext
)
const
autocompleteValue
=
Object
.
keys
(
query
).
map
(
quantity
=>
`
${
quantity
}
=
${
query
[
quantity
]}
`
)
const
autocompleteValue
=
Object
.
keys
(
query
).
map
(
quantity
=>
`
${
quantity
}
=
${
query
[
quantity
]}
`
)
...
@@ -33,36 +39,52 @@ export default function SearchBar() {
...
@@ -33,36 +39,52 @@ export default function SearchBar() {
}
}
}
}
const
loadValues
=
(
quantity
,
value
)
=>
{
const
filterOptions
=
useCallback
((
options
,
params
)
=>
{
setLoading
(
true
)
const
[
quantity
,
value
]
=
params
.
inputValue
.
split
(
'
=
'
)
const
size
=
searchQuantities
[
quantity
].
statistic_size
||
20
const
filteredOptions
=
options
.
filter
(
option
=>
{
api
.
quantity_search
(
quantity
,
query
,
size
,
true
)
const
[
optionQuantity
,
optionValue
]
=
option
.
split
(
'
=
'
)
.
then
(
response
=>
{
if
(
!
value
)
{
setLoading
(
false
)
return
optionQuantity
.
includes
(
quantity
)
||
optionQuantity
===
quantity
const
options
=
Object
.
keys
(
response
.
quantity
.
values
).
map
(
value
=>
`
${
quantity
}
=
${
value
}
`
)
}
else
{
setOptions
(
options
)
return
optionValue
.
includes
(
value
)
||
optionValue
===
value
setOpen
(
true
)
}
})
})
.
catch
(()
=>
{
return
filteredOptions
setLoading
(
false
)
},
[])
})
}
const
handleInputChange
=
(
event
,
value
,
reason
)
=>
{
const
loadOptions
=
useCallback
((
quantity
,
value
)
=>
{
if
(
suggestionsTimerRef
.
current
!==
null
)
{
clearTimeout
(
suggestionsTimerRef
.
current
)
}
suggestionsTimerRef
.
current
=
setTimeout
(()
=>
{
setLoading
(
true
)
api
.
suggestions_search
(
quantity
,
query
,
value
,
20
,
true
)
.
then
(
response
=>
{
setLoading
(
false
)
const
options
=
response
.
suggestions
.
map
(
value
=>
`
${
quantity
}
=
${
value
}
`
)
setOptions
(
options
)
setOpen
(
true
)
})
.
catch
(()
=>
{
setLoading
(
false
)
})
},
200
)
},
[
api
,
suggestionsTimerRef
])
const
handleInputChange
=
useCallback
((
event
,
value
,
reason
)
=>
{
if
(
reason
===
'
input
'
)
{
if
(
reason
===
'
input
'
)
{
setInputValue
(
value
)
setInputValue
(
value
)
const
[
quantity
,
quantityValue
]
=
value
.
split
(
'
=
'
)
const
[
quantity
,
quantityValue
]
=
value
.
split
(
'
=
'
)
if
(
!
quantityValue
)
{
if
(
searchQuantities
[
quantity
])
{
if
(
searchQuantities
[
quantity
])
{
loadValues
(
quantity
,
quantityValue
)
loadOptions
(
quantity
,
quantityValue
)
}
else
{
}
else
{
setOptions
(
defaultOptions
)
setOptions
(
defaultOptions
)
}
}
}
}
}
}
}
,
[
loadOptions
])
const
handleChange
=
(
event
,
entries
,
reason
)
=>
{
const
handleChange
=
(
event
,
entries
)
=>
{
const
newQuery
=
entries
.
reduce
((
query
,
entry
)
=>
{
const
newQuery
=
entries
.
reduce
((
query
,
entry
)
=>
{
if
(
entry
)
{
if
(
entry
)
{
const
[
quantity
,
value
]
=
entry
.
split
(
'
=
'
)
const
[
quantity
,
value
]
=
entry
.
split
(
'
=
'
)
...
@@ -91,7 +113,7 @@ export default function SearchBar() {
...
@@ -91,7 +113,7 @@ export default function SearchBar() {
setInputValue
(
''
)
setInputValue
(
''
)
}
else
{
}
else
{
setInputValue
(
`
${
entry
}
=`
)
setInputValue
(
`
${
entry
}
=`
)
load
Value
s
(
quantity
)
load
Option
s
(
quantity
)
}
}
}
}
}
}
...
@@ -119,9 +141,9 @@ export default function SearchBar() {
...
@@ -119,9 +141,9 @@ export default function SearchBar() {
onChange
=
{
handleChange
}
onChange
=
{
handleChange
}
onInputChange
=
{
handleInputChange
}
onInputChange
=
{
handleInputChange
}
getOptionSelected
=
{(
option
,
value
)
=>
option
===
value
}
getOptionSelected
=
{(
option
,
value
)
=>
option
===
value
}
getOptionLabel
=
{(
option
)
=>
option
}
options
=
{
options
}
options
=
{
options
}
loading
=
{
loading
}
loading
=
{
loading
}
filterOptions
=
{
filterOptions
}
renderInput
=
{(
params
)
=>
(
renderInput
=
{(
params
)
=>
(
<
TextField
<
TextField
{...
params
}
{...
params
}
...
...
nomad/app/api/repo.py
View file @
05b81af7
...
@@ -566,6 +566,8 @@ _repo_quantity_search_request_parser.add_argument(
...
@@ -566,6 +566,8 @@ _repo_quantity_search_request_parser.add_argument(
'after'
,
type
=
str
,
help
=
'The after value to use for "scrolling".'
)
'after'
,
type
=
str
,
help
=
'The after value to use for "scrolling".'
)
_repo_quantity_search_request_parser
.
add_argument
(
_repo_quantity_search_request_parser
.
add_argument
(
'size'
,
type
=
int
,
help
=
'The max size of the returned values.'
)
'size'
,
type
=
int
,
help
=
'The max size of the returned values.'
)
_repo_quantity_search_request_parser
.
add_argument
(
'value'
,
type
=
str
,
help
=
'A partial value. Only values that include this will be returned'
)
_repo_quantity_model
=
api
.
model
(
'RepoQuantity'
,
{
_repo_quantity_model
=
api
.
model
(
'RepoQuantity'
,
{
'after'
:
fields
.
String
(
description
=
'The after value that can be used to retrieve the next set of values.'
),
'after'
:
fields
.
String
(
description
=
'The after value that can be used to retrieve the next set of values.'
),
...
@@ -631,6 +633,62 @@ class RepoQuantityResource(Resource):
...
@@ -631,6 +633,62 @@ class RepoQuantityResource(Resource):
abort
(
400
,
'Given quantity does not exist: %s'
%
str
(
e
))
abort
(
400
,
'Given quantity does not exist: %s'
%
str
(
e
))
_repo_suggestions_search_request_parser
=
api
.
parser
()
add_search_parameters
(
_repo_suggestions_search_request_parser
)
_repo_suggestions_search_request_parser
.
add_argument
(
'size'
,
type
=
int
,
help
=
'The max size of the returned values.'
)
_repo_suggestions_search_request_parser
.
add_argument
(
'include'
,
type
=
str
,
help
=
'A substring that all values need to include.'
)
_repo_suggestions_model
=
api
.
model
(
'RepoSuggestionsValues'
,
{
'suggestions'
:
fields
.
List
(
fields
.
String
,
description
=
'A list with the suggested values.'
)
})
@
ns
.
route
(
'/suggestions/<string:quantity>'
)
class
RepoSuggestionsResource
(
Resource
):
@
api
.
doc
(
'suggestions_search'
)
@
api
.
response
(
400
,
'Invalid requests, e.g. wrong owner type, bad quantity, bad search parameters'
)
@
api
.
expect
(
_repo_suggestions_search_request_parser
,
validate
=
True
)
@
api
.
marshal_with
(
_repo_suggestions_model
,
skip_none
=
True
,
code
=
200
,
description
=
'Suggestions send'
)
@
authenticate
()
def
get
(
self
,
quantity
:
str
):
'''
Retrieve the top values for the given quantity from entries matching the search.
Values can be filtered by to include a given value.
There is no ordering, no pagination, and no scroll interface.
The result will contain a 'suggestions' key with values. There will be upto 'size' many values.
'''
search_request
=
search
.
SearchRequest
()
args
=
{
key
:
value
for
key
,
value
in
_repo_suggestions_search_request_parser
.
parse_args
().
items
()
if
value
is
not
None
}
apply_search_parameters
(
search_request
,
args
)
size
=
args
.
get
(
'size'
,
20
)
include
=
args
.
get
(
'include'
,
None
)
try
:
assert
size
>=
0
except
AssertionError
:
abort
(
400
,
message
=
'invalid size'
)
try
:
search_request
.
statistic
(
quantity
,
size
=
size
,
include
=
include
)
results
=
search_request
.
execute
()
results
[
'suggestions'
]
=
list
(
results
[
'statistics'
][
quantity
].
keys
())
return
results
,
200
except
KeyError
as
e
:
import
traceback
traceback
.
print_exc
()
abort
(
400
,
'Given quantity does not exist: %s'
%
str
(
e
))
_repo_quantities_search_request_parser
=
api
.
parser
()
_repo_quantities_search_request_parser
=
api
.
parser
()
add_search_parameters
(
_repo_quantities_search_request_parser
)
add_search_parameters
(
_repo_quantities_search_request_parser
)
_repo_quantities_search_request_parser
.
add_argument
(
_repo_quantities_search_request_parser
.
add_argument
(
...
...
nomad/search.py
View file @
05b81af7
...
@@ -297,7 +297,7 @@ class SearchRequest:
...
@@ -297,7 +297,7 @@ class SearchRequest:
def
statistic
(
def
statistic
(
self
,
quantity_name
:
str
,
size
:
int
,
metrics_to_use
:
List
[
str
]
=
[],
self
,
quantity_name
:
str
,
size
:
int
,
metrics_to_use
:
List
[
str
]
=
[],
order
:
Dict
[
str
,
str
]
=
dict
(
_key
=
'asc'
)):
order
:
Dict
[
str
,
str
]
=
dict
(
_key
=
'asc'
)
,
include
:
str
=
None
):
'''
'''
This can be used to display statistics over the searched entries and allows to
This can be used to display statistics over the searched entries and allows to
implement faceted search on the top values for each quantity.
implement faceted search on the top values for each quantity.
...
@@ -322,9 +322,15 @@ class SearchRequest:
...
@@ -322,9 +322,15 @@ class SearchRequest:
``unique_code_runs``, ``datasets``, other domain specific metrics.
``unique_code_runs``, ``datasets``, other domain specific metrics.
The basic doc_count metric ``code_runs`` is always given.
The basic doc_count metric ``code_runs`` is always given.
order: The order dictionary is passed to the elastic search aggregation.
order: The order dictionary is passed to the elastic search aggregation.
include:
Uses an regular expression in ES to only return values that include
the given substring.
'''
'''
quantity
=
search_quantities
[
quantity_name
]
quantity
=
search_quantities
[
quantity_name
]
terms
=
A
(
'terms'
,
field
=
quantity
.
search_field
,
size
=
size
,
order
=
order
)
terms_kwargs
=
{}
if
include
is
not
None
:
terms_kwargs
[
'include'
]
=
'.*%s.*'
%
include
terms
=
A
(
'terms'
,
field
=
quantity
.
search_field
,
size
=
size
,
order
=
order
,
**
terms_kwargs
)
buckets
=
self
.
_search
.
aggs
.
bucket
(
'statistics:%s'
%
quantity_name
,
terms
)
buckets
=
self
.
_search
.
aggs
.
bucket
(
'statistics:%s'
%
quantity_name
,
terms
)
self
.
_add_metrics
(
buckets
,
metrics_to_use
)
self
.
_add_metrics
(
buckets
,
metrics_to_use
)
...
...
tests/app/test_api.py
View file @
05b81af7
...
@@ -1048,6 +1048,23 @@ class TestRepo():
...
@@ -1048,6 +1048,23 @@ class TestRepo():
rv
=
api
.
get
(
'/repo/?owner=user'
)
rv
=
api
.
get
(
'/repo/?owner=user'
)
assert
rv
.
status_code
==
401
assert
rv
.
status_code
==
401
@
pytest
.
mark
.
parametrize
(
'suggestions, quantity, value'
,
[
(
1
,
'dft.system'
,
'bulk'
),
(
1
,
'dft.system'
,
'ulk'
),
(
1
,
'dft.system'
,
'ul'
),
(
0
,
'dft.system'
,
'notbulk'
),
(
1
,
'dft.system'
,
None
)
])
def
test_suggestions_search
(
self
,
api
,
example_elastic_calcs
,
no_warn
,
test_user_auth
,
suggestions
,
quantity
,
value
):
url
=
'/repo/suggestions/%s'
%
quantity
if
value
is
not
None
:
url
=
url
+
'?include=%s'
%
value
rv
=
api
.
get
(
url
,
headers
=
test_user_auth
)
assert
rv
.
status_code
==
200
data
=
json
.
loads
(
rv
.
data
)
values
=
data
[
'suggestions'
]
assert
len
(
values
)
==
suggestions
@
pytest
.
mark
.
parametrize
(
'calcs, quantity, value'
,
[
@
pytest
.
mark
.
parametrize
(
'calcs, quantity, value'
,
[
(
2
,
'dft.system'
,
'bulk'
),
(
2
,
'dft.system'
,
'bulk'
),
(
0
,
'dft.system'
,
'atom'
),
(
0
,
'dft.system'
,
'atom'
),
...
...
tests/test_search.py
View file @
05b81af7
...
@@ -170,6 +170,14 @@ def test_search_statistics(elastic, example_search_data):
...
@@ -170,6 +170,14 @@ def test_search_statistics(elastic, example_search_data):
assert
'quantities'
not
in
results
assert
'quantities'
not
in
results
def
test_suggest_statistics
(
elastic
,
example_search_data
):
results
=
SearchRequest
(
domain
=
'dft'
).
statistic
(
'dft.system'
,
include
=
'ulk'
,
size
=
2
).
execute
()
assert
len
(
results
[
'statistics'
][
'dft.system'
])
==
1
results
=
SearchRequest
(
domain
=
'dft'
).
statistic
(
'dft.system'
,
include
=
'not_ulk'
,
size
=
2
).
execute
()
assert
len
(
results
[
'statistics'
][
'dft.system'
])
==
0
def
test_search_totals
(
elastic
,
example_search_data
):
def
test_search_totals
(
elastic
,
example_search_data
):
use_metrics
=
search_extension
.
metrics
.
keys
()
use_metrics
=
search_extension
.
metrics
.
keys
()
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment