diff --git a/gui/src/components/search/FilterContext.js b/gui/src/components/search/FilterContext.js index 19292898403ac30310695d15842dade4d2bf757d..b1db610d7bfb4923f9942832cec0cf72d087c802 100644 --- a/gui/src/components/search/FilterContext.js +++ b/gui/src/components/search/FilterContext.js @@ -47,6 +47,7 @@ export const quantityAbbreviations = new Map() export const quantityFullnames = new Map() export const quantityMaterialNames = {} export const quantityEntryNames = {} +export const quantityPostfixes = {} export const labelMaterial = 'Material' export const labelElements = 'Elements / Formula' export const labelSymmetry = 'Symmetry' @@ -80,7 +81,7 @@ export const labelIDs = 'IDs' * @param {string} agg Possible aggregation associated with the quantity. If * provided, the initial aggregation value will be prefetched for this quantity. */ -function registerQuantity(name, group, agg) { +function registerQuantity(name, group, agg, postfix = 'any') { // Store the available quantities, their grouping, and the initial aggregation // types. quantities.add(name) @@ -112,13 +113,16 @@ function registerQuantity(name, group, agg) { } quantityMaterialNames[name] = materialName quantityEntryNames[materialName] = name + + // Store the default query postfixes (any/all) for each quantity + quantityPostfixes[name] = postfix } registerQuantity('results.material.structural_type', labelMaterial, 'terms') registerQuantity('results.material.functional_type', labelMaterial, 'terms') registerQuantity('results.material.compound_type', labelMaterial, 'terms') registerQuantity('results.material.material_name', labelMaterial) -registerQuantity('results.material.elements', labelElements, 'terms') +registerQuantity('results.material.elements', labelElements, 'terms', 'all') registerQuantity('results.material.elements_exclusive', labelElements, 'terms') registerQuantity('results.material.chemical_formula_hill', labelElements) registerQuantity('results.material.chemical_formula_anonymous', labelElements) @@ -136,10 +140,12 @@ registerQuantity('results.method.simulation.program_name', labelMethod, 'terms') registerQuantity('results.method.simulation.program_version', labelMethod) registerQuantity('results.method.simulation.dft.basis_set_type', labelDFT, 'terms') registerQuantity('results.method.simulation.dft.core_electron_treatment', labelDFT, 'terms') +registerQuantity('results.method.simulation.dft.xc_functional_type', labelDFT, 'terms') registerQuantity('results.method.simulation.dft.relativity_method', labelDFT, 'terms') +registerQuantity('results.method.simulation.gw.gw_type', labelGW, 'terms') registerQuantity('results.properties.electronic.band_structure_electronic.channel_info.band_gap', labelElectronic, 'min_max') registerQuantity('results.properties.electronic.band_structure_electronic.channel_info.band_gap_type', labelElectronic, 'terms') -registerQuantity('results.properties.available_properties', labelElectronic, 'terms') +registerQuantity('results.properties.available_properties', labelElectronic, 'terms', 'all') registerQuantity('external_db', labelAuthor, 'terms') registerQuantity('authors.name', labelAuthor) registerQuantity('upload_time', labelAuthor, 'min_max') @@ -195,7 +201,7 @@ export const SearchContext = React.memo(({ } } } - aggRequest = cleanAggRequest(aggRequest, resource) + aggRequest = toAPIAgg(aggRequest, resource) const search = { owner: 'visible', query: {}, @@ -205,7 +211,7 @@ export const SearchContext = React.memo(({ api.query(resource, search, false) .then(data => { - data = cleanAggResponse(data, resource) + data = toGUIAgg(data, resource) setInitialAggs(data) }) }, [api, setInitialAggs, resource]) @@ -579,7 +585,7 @@ export function useAgg(quantity, type, restrict = false, update = true, delay = if (restrict && query && quantity in query) { queryCleaned[quantity] = undefined } - queryCleaned = cleanQuery(queryCleaned, exclusive, resource) + queryCleaned = toAPIQuery(queryCleaned, exclusive, resource) let aggRequest = { [quantity]: { [type]: { @@ -589,7 +595,7 @@ export function useAgg(quantity, type, restrict = false, update = true, delay = } } - aggRequest = cleanAggRequest(aggRequest, resource) + aggRequest = toAPIAgg(aggRequest, resource) const search = { owner: query.owner || 'visible', query: queryCleaned, @@ -600,7 +606,7 @@ export function useAgg(quantity, type, restrict = false, update = true, delay = api.query(resource, search, false) .then(data => { - data = cleanAggResponse(data, resource) + data = toGUIAgg(data, resource) data = getAggData(data, quantity, type) firstLoad.current = false setResults(data) @@ -665,7 +671,7 @@ export function useScrollResults(pageSize, orderBy, order, exclusive, delay = 50 // one with the data. const apiCall = useCallback((query, pageSize, orderBy, order, exclusive) => { pageAfterValue.current = undefined - const cleanedQuery = cleanQuery(query, exclusive, resource) + const cleanedQuery = toAPIQuery(query, exclusive, resource) const search = { owner: query.owner || 'visible', query: cleanedQuery, @@ -746,9 +752,7 @@ export function useScrollResults(pageSize, orderBy, order, exclusive, delay = 50 } /** - * Converts all sets to arrays and convert all Quantities into their SI unit - * values. Also remaps quantity names depending whether materials or entries are - * requested. + * Converts the contents a query into a format that is suitable for the API. * * Should only be called when making the final API call, as during the * construction of the query it is much more convenient to store filters within @@ -760,12 +764,10 @@ export function useScrollResults(pageSize, orderBy, order, exclusive, delay = 50 * @returns {object} A copy of the object with certain items cleaned into a * format that is supported by the API. */ -export function cleanQuery(query, exclusive, resource) { +export function toAPIQuery(query, exclusive, resource) { let newQuery = {} for (let [k, v] of Object.entries(query)) { let newValue - let postfix - // Some quantities are not included in the query, e.g. the owner. if (k === 'owner') { // If a regular elements query is made, we add the ':all'-prefix. @@ -774,7 +776,6 @@ export function cleanQuery(query, exclusive, resource) { if (v.size === 0) { continue } - postfix = 'all' newValue = setToArray(v) // If an exlusive elements query is made, we sort the elements and // concatenate them into a single string. This value we can then use to @@ -786,29 +787,19 @@ export function cleanQuery(query, exclusive, resource) { } newValue = setToArray(v).sort().join(' ') } else { - if (v instanceof Set) { - newValue = setToArray(v) - if (newValue.length === 0) { - newValue = undefined - } else { - newValue = newValue.map((item) => item instanceof Quantity ? item.toSI() : item) + if (isPlainObject(v)) { + newValue = {} + if (!isNil(v.lte)) { + newValue.lte = toAPIQueryValue(v.lte) } - postfix = 'any' - } else if (v instanceof Quantity) { - newValue = v.toSI() - } else if (isArray(v)) { - if (v.length === 0) { - newValue = undefined - } else { - newValue = v.map((item) => item instanceof Quantity ? item.toSI() : item) + if (!isNil(v.gte)) { + newValue.gte = toAPIQueryValue(v.gte) } - postfix = 'any' - } else if (isPlainObject(v)) { - newValue = cleanQuery(v, exclusive, resource) } else { - newValue = v + newValue = toAPIQueryValue(v) } } + const postfix = isArray(newValue) ? quantityPostfixes[k] : undefined k = resource === 'materials' ? quantityMaterialNames[k] : k k = postfix ? `${k}:${postfix}` : k newQuery[k] = newValue @@ -817,13 +808,43 @@ export function cleanQuery(query, exclusive, resource) { return newQuery } +/** + * Cleans a filter value into a form that is supported by the API. This includes: + * - Sets are transformed into Arrays + * - Quantities are converted to SI values. + * + * @returns {any} The filter value in a format that is suitable for the API. + */ +function toAPIQueryValue(value) { + let newValue + if (value instanceof Set) { + newValue = setToArray(value) + if (newValue.length === 0) { + newValue = undefined + } else { + newValue = newValue.map((item) => item instanceof Quantity ? item.toSI() : item) + } + } else if (value instanceof Quantity) { + newValue = value.toSI() + } else if (isArray(value)) { + if (value.length === 0) { + newValue = undefined + } else { + newValue = value.map((item) => item instanceof Quantity ? item.toSI() : item) + } + } else { + newValue = value + } + return newValue +} + /** * Cleans the aggregation request into a format that is usable by the API. * * @returns {object} A copy of the object with the correct quantity names used * by the API. */ -function cleanAggRequest(aggs, resource) { +function toAPIAgg(aggs, resource) { if (resource === 'materials') { let newAggs = {} for (let [label, agg] of Object.entries(aggs)) { @@ -844,7 +865,7 @@ function cleanAggRequest(aggs, resource) { * @returns {object} A copy of the object with the correct quantity names used * by the GUI. */ -function cleanAggResponse(response, resource) { +function toGUIAgg(response, resource) { if (resource === 'materials') { const newAggs = {} for (let [label, agg] of Object.entries(response.aggregations)) { diff --git a/gui/src/components/search/menus/FilterSubMenuSimulation.js b/gui/src/components/search/menus/FilterSubMenuSimulation.js new file mode 100644 index 0000000000000000000000000000000000000000..394fd28a42508ffb2f45fea251cc30266fda3590 --- /dev/null +++ b/gui/src/components/search/menus/FilterSubMenuSimulation.js @@ -0,0 +1,52 @@ +/* + * Copyright The NOMAD Authors. + * + * This file is part of NOMAD. See https://nomad-lab.eu for further info. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useContext } from 'react' +import PropTypes from 'prop-types' +import { Grid } from '@material-ui/core' +import { FilterSubMenu, filterMenuContext } from './FilterMenu' +import InputSelect from '../input/InputSelect' +import InputText from '../input/InputText' + +const FilterSubMenuDFT = React.memo(({ + value, + ...rest +}) => { + const {selected} = useContext(filterMenuContext) + const visible = value === selected + + return <FilterSubMenu value={value} {...rest}> + <Grid container spacing={2}> + <Grid item xs={12}> + <InputSelect + quantity="results.method.simulation.program_name" + visible={visible} + /> + </Grid> + <Grid item xs={12}> + <InputText + quantity="results.method.simulation.program_version" + /> + </Grid> + </Grid> + </FilterSubMenu> +}) +FilterSubMenuDFT.propTypes = { + value: PropTypes.string +} + +export default FilterSubMenuDFT diff --git a/nomad/datamodel/datamodel.py b/nomad/datamodel/datamodel.py index 27d4a9dee3cc251e4951fd65c687486627cdede9..b4e09ea714f454bdd1fd58431cfd4d08ba49b91e 100644 --- a/nomad/datamodel/datamodel.py +++ b/nomad/datamodel/datamodel.py @@ -555,7 +555,7 @@ class EntryMetadata(metainfo.MSection): description='The full name of the authors for exact searches', metric='cardinality', many_or='append', search_field='authors.name.keyword'), - a_elasticsearch=Elasticsearch()) + a_elasticsearch=Elasticsearch(material_entry_type)) shared_with = metainfo.Quantity( type=user_reference, shape=['0..*'], default=[], categories=[MongoMetadata, EditableUserMetadata],