From b6bad818022a1d89d1bf16d21b22cbe6dfb4677c Mon Sep 17 00:00:00 2001 From: Mohammad Nakhaee <nakhaee@physik.hu-berlin.de> Date: Fri, 3 May 2024 11:48:25 +0000 Subject: [PATCH] Resolve "Consistent labels in archive browser and ELNs" Changelog: Changed --- docs/howto/customization/basics.md | 21 + docs/howto/customization/elns.md | 8 + gui/src/components/Quantity.js | 18 +- gui/src/components/archive/ArchiveBrowser.js | 60 ++- gui/src/components/archive/Browser.js | 64 ++- gui/src/components/archive/MetainfoBrowser.js | 36 +- gui/src/components/conftest.spec.js | 3 +- gui/src/components/datatable/Datatable.js | 6 +- .../editQuantity/AuthorEditQuantity.js | 9 +- .../editQuantity/AutocompleteEditQuantity.js | 6 + .../editQuantity/BoolEditQuantity.js | 9 +- .../editQuantity/DateTimeEditQuantity.js | 8 +- .../editQuantity/EnumEditQuantity.js | 6 + .../editQuantity/FileEditQuantity.js | 9 +- .../editQuantity/ListEditQuantity.js | 8 +- .../editQuantity/NumberEditQuantity.js | 6 + .../editQuantity/RadioEnumEditQuantity.js | 9 +- .../editQuantity/ReferenceEditQuantity.js | 11 +- .../editQuantity/RichTextEditQuantity.js | 7 +- .../editQuantity/SliderEditQuantity.js | 7 +- .../editQuantity/StringEditQuantity.js | 17 +- .../components/editQuantity/display.spec.js | 247 +++++++++++ gui/src/components/entry/EntryDetails.js | 4 +- gui/src/components/entry/OverviewView.spec.js | 34 +- gui/src/components/entry/conftest.spec.js | 4 +- .../entry/properties/MaterialCard.js | 2 +- .../entry/properties/MaterialCardTopology.js | 3 +- gui/src/components/search/Filter.js | 17 +- gui/src/components/search/FilterRegistry.js | 54 +-- .../components/search/FilterSummary.spec.js | 8 +- gui/src/components/search/FilterTitle.js | 8 +- gui/src/components/search/FilterTitle.spec.js | 6 +- gui/src/components/search/SearchContext.js | 4 + .../components/search/SearchContext.spec.js | 1 - gui/src/components/search/conftest.spec.js | 4 +- .../search/widgets/Dashboard.spec.js | 4 +- gui/src/components/search/widgets/Widget.js | 7 +- .../search/widgets/WidgetHeader.spec.js | 2 +- .../search/widgets/WidgetHistogram.js | 15 +- .../search/widgets/WidgetPeriodicTable.js | 17 +- .../search/widgets/WidgetScatterPlot.js | 215 +-------- .../search/widgets/WidgetScatterPlot.spec.js | 6 +- .../search/widgets/WidgetScatterPlotEdit.js | 8 + .../components/search/widgets/WidgetTerms.js | 16 +- gui/src/components/uploads/ProcessingTable.js | 2 +- gui/src/components/uploads/UploadPage.spec.js | 28 +- gui/src/utils.js | 28 ++ gui/tests/artifacts.js | 36 +- gui/tests/env.js | 115 +---- nomad/cli/dev.py | 2 + nomad/config/defaults.yaml | 412 ++++++------------ nomad/config/models/ui.py | 3 + nomad/datamodel/metainfo/annotations.py | 10 +- nomad/datamodel/metainfo/basesections.py | 30 +- nomad/datamodel/metainfo/eln/__init__.py | 10 +- 55 files changed, 875 insertions(+), 815 deletions(-) create mode 100644 gui/src/components/editQuantity/display.spec.js diff --git a/docs/howto/customization/basics.md b/docs/howto/customization/basics.md index b8348c8d09..c5ad63ce06 100644 --- a/docs/howto/customization/basics.md +++ b/docs/howto/customization/basics.md @@ -353,3 +353,24 @@ above: !!! warning "Attention" You cannot create definitions that lead to circular loading of `*.archive.yaml` files. Each `definitions` section in an NOMAD entry represents a *schema package*. Each *schema package* needs to be fully loaded and analyzed before it can be used by other *schema packages* in other entries. Therefore, two *schema packages* in two entries cannot reference each other. + +## Conventions + +### Conventions for labels + +When assigning labels within your codebase, it's essential to follow consistent naming +conventions for clarity and maintainability. The following guidelines outline the +conventions for labeling different elements: + +- **Sections**: Labels for sections should adhere to Python convention of CapitalizedCamelCase. +This means that each word in the label should begin with a capital letter, and there should be +no spaces between words. For example: `SectionLabelOne`, `SectionLabelTwo`. + +- **Quantities and Subsections**: Labels for quantities and subsections should be in +lower_case. This convention involves writing all lowercase letters and separating +words with whitespace. Abbreviations within these labels may be capitalized to +enhance scientific readability. For example: `quantity label`, `subsection label`, `IV label`. + + + + diff --git a/docs/howto/customization/elns.md b/docs/howto/customization/elns.md index dc03d30dcc..37785b445b 100644 --- a/docs/howto/customization/elns.md +++ b/docs/howto/customization/elns.md @@ -26,3 +26,11 @@ The is the commented ELN schema from our ELN example upload that can be created --8<-- "examples/data/eln/schema.archive.yaml" ``` +--- +**NOTE: Defining Labels for Quantities** + +When defining labels for quantities, utilize the +[display annotations](../../reference/annotations.md#display-annotations) +and ensure that you follow the conventions as described +[here](./basics.md#conventions-for-labels). +--- diff --git a/gui/src/components/Quantity.js b/gui/src/components/Quantity.js index 484c8545f3..157deafd4f 100644 --- a/gui/src/components/Quantity.js +++ b/gui/src/components/Quantity.js @@ -40,7 +40,7 @@ import { searchQuantities } from '../config' import Placeholder from './visualization/Placeholder' import Ellipsis from './visualization/Ellipsis' import NoData from './visualization/NoData' -import { formatNumber, formatTimestamp, authorList, serializeMetainfo } from '../utils' +import {formatNumber, formatTimestamp, authorList, serializeMetainfo, getDisplayLabel} from '../utils' import { Quantity as Q } from './units/Quantity' import { Unit } from './units/Unit' import { useUnitContext } from './units/UnitContext' @@ -179,10 +179,10 @@ const Quantity = React.memo((props) => { if (!useLabel) { // Primarily use a lowercase 'pretty' label if one is defined in FilterRegistry if (isQuantityString && defaultFilterData?.[quantity]?.label) { - useLabel = defaultFilterData?.[quantity]?.label.toLowerCase() + useLabel = defaultFilterData?.[quantity]?.label // Alternatively use the original name in metainfo, underscores replaced by spaces } else if (def?.name) { - useLabel = def.name.replace(/_/g, ' ') + useLabel = getDisplayLabel(def) } else if (isQuantityString) { useLabel = quantity } else { @@ -343,7 +343,7 @@ const nElementMap = { } const quantityPresets = { datasets: { - label: 'datasets', + label: 'Datasets', placeholder: 'no datasets', render: (data) => (data.datasets && data.datasets.length !== 0) && <div> @@ -355,14 +355,14 @@ const quantityPresets = { </div> }, authors: { - label: 'authors', + label: 'Authors', placeholder: 'no authors', render: (data) => <Typography> {authorList(data || [], true)} </Typography> }, references: { - label: 'references', + label: 'References', placeholder: 'no references', render: (data) => (data?.references && <div style={{display: 'inline-grid'}}> {data.references.map(ref => <Typography key={ref} noWrap> @@ -371,7 +371,7 @@ const quantityPresets = { </div>) }, comment: { - label: 'comment', + label: 'Comment', placeholder: 'no comment' }, upload_id: { @@ -394,7 +394,7 @@ const quantityPresets = { }, last_processing_version: { description: 'Version used in the last processing', - label: 'processing version', + label: 'Processing version', noWrap: true, placeholder: 'not processed', render: (data) => <Typography noWrap> @@ -450,7 +450,7 @@ const quantityPresets = { }, 'results.material.n_elements': { noWrap: true, - label: "number of elements", + label: "Number of elements", render: (data) => { const n = get(data, 'results.material.n_elements') const label = nElementMap[n] diff --git a/gui/src/components/archive/ArchiveBrowser.js b/gui/src/components/archive/ArchiveBrowser.js index a815d854b9..c99164c612 100644 --- a/gui/src/components/archive/ArchiveBrowser.js +++ b/gui/src/components/archive/ArchiveBrowser.js @@ -24,7 +24,7 @@ import { } from '@material-ui/core' import {useHistory, useRouteMatch} from 'react-router-dom' import Browser, { - Adaptor, browserContext, Compartment, Content, formatSubSectionName, Item, ItemChip, laneContext, useLane + Adaptor, browserContext, Compartment, Content, Item, ItemChip, laneContext, useLane } from './Browser' import { RawFileAdaptor } from './FileBrowser' import { @@ -55,7 +55,8 @@ import SectionEditor from './SectionEditor' import PlotlyFigure from './PlotlyFigure' import { appendDataUrl, createEntryUrl, createUploadUrl, formatTimestamp, parseNomadUrl, refType, resolveInternalRef, - resolveNomadUrl, systemMetainfoUrl, titleCase, isWaitingForUpdateTestId, resolveNomadUrlNoThrow, getOptions + resolveNomadUrl, systemMetainfoUrl, titleCase, isWaitingForUpdateTestId, resolveNomadUrlNoThrow, getOptions, + getDisplayLabel } from '../../utils' import { EntryButton } from '../nav/Routes' import NavigateIcon from '@material-ui/icons/ArrowForward' @@ -271,7 +272,7 @@ const ArchiveConfigForm = React.memo(function ArchiveConfigForm({searchOptions, onChange={handleConfigChange} name="showMeta"/> } - label="definitions" + label="technical view" /> </Tooltip> {entryId && <Download @@ -415,7 +416,7 @@ class ArchiveAdaptor extends Adaptor { * @param {*} obj The data that objUrl points to = a location in some archive. * @param {*} def The metainfo definition of obj */ - constructor(objUrl, obj, def, isInEln) { + constructor(objUrl, obj, def, property, isInEln) { super() this.objUrl = objUrl this.parsedObjUrl = parseNomadUrl(objUrl) @@ -428,6 +429,7 @@ class ArchiveAdaptor extends Adaptor { this.isInEln = isInEln === undefined && def.m_def === SectionMDef ? isEditable(def) : isInEln this.obj = obj // The data in the archive tree to display this.def = def + this.property = property this.external_refs = {} } @@ -439,7 +441,16 @@ class ArchiveAdaptor extends Adaptor { this.entryIsEditable = editable } - async adaptorFactory(objUrl, obj, def) { + async adaptorFactory(objUrl, obj, property) { + let def + if (property.m_def === SubSectionMDef) { + def = property.sub_section + } else if (property.m_def === QuantityMDef) { + def = property + } else if (property.m_def === AttributeMDef) { + def = property + } + if (obj.m_def === PackageMDef) { // We're viewing an archive which contains metainfo definitions, and open the definitions node const metainfo = await this.dataStore.getMetainfoAsync(objUrl) @@ -455,7 +466,7 @@ class ArchiveAdaptor extends Adaptor { def = await this.dataStore.getMetainfoDefAsync(newDefUrl) } const isInEln = this.isInEln || isEditable(def) - return new SectionAdaptor(objUrl, obj, def, isInEln) + return new SectionAdaptor(objUrl, obj, def, property, isInEln) } if (def.m_def === QuantityMDef) { @@ -480,6 +491,10 @@ class ArchiveAdaptor extends Adaptor { return metainfoAdaptorFactory(this.def) } + if (key === '_subsectionmetainfo') { + return metainfoAdaptorFactory(this.property) + } + if (key.startsWith('_external_ref')) { const ref = this.external_refs[key] if (!ref) return null @@ -511,16 +526,15 @@ class SectionAdaptor extends ArchiveAdaptor { if (!property) { return super.itemAdaptor(key) } else if (property.m_def === SubSectionMDef) { - const sectionDef = property.sub_section let subSectionAdaptor let subSectionIndex = -1 if (property.repeats) { subSectionIndex = index || 0 subSectionAdaptor = await this.adaptorFactory( - appendDataUrl(this.parsedObjUrl, `${name}/${subSectionIndex}`), value[subSectionIndex], sectionDef) + appendDataUrl(this.parsedObjUrl, `${name}/${subSectionIndex}`), value[subSectionIndex], property) } else { subSectionAdaptor = await this.adaptorFactory( - appendDataUrl(this.parsedObjUrl, name), value, sectionDef) + appendDataUrl(this.parsedObjUrl, name), value, property) } subSectionAdaptor.parentRelation = { parent: this.obj, @@ -602,6 +616,7 @@ class SectionAdaptor extends ArchiveAdaptor { return <Section section={this.obj} def={this.def} + property={this.property} parentRelation={this.parentRelation} sectionIsInEln={this.isInEln} sectionIsEditable={this.entryIsEditable && this.isInEln} @@ -920,7 +935,7 @@ export function getAllVisibleProperties(sectionDef) { return [...quantities, ...sub_sections] } -export function Section({section, def, parentRelation, sectionIsEditable, sectionIsInEln}) { +export function Section({section, def, property, parentRelation, sectionIsEditable, sectionIsInEln}) { const {handleArchiveChanged, uploadId, entryId} = useEntryStore() || {} const config = useRecoilValue(configState) const [showJson, setShowJson] = useState(false) @@ -1002,13 +1017,14 @@ export function Section({section, def, parentRelation, sectionIsEditable, sectio const renderQuantityItem = useCallback((key, quantityName, quantityDef, value, disabled) => { const itemKey = quantityName ? `${key}:${quantityName}` : key const isDefault = value !== undefined && value !== null && (section[key] === undefined || section[key] === null) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) return ( <Box key={itemKey} data-testid={"visible-quantity"}> <Item itemKey={itemKey} disabled={disabled}> <Box component="span" whiteSpace="nowrap" style={{maxWidth: 100, overflow: 'ellipses'}}> <Typography component="span"> <Box fontWeight="bold" component="span"> - {quantityName || quantityDef.name} + {quantityName || label} </Box> </Typography>{!disabled && <span> = @@ -1023,7 +1039,7 @@ export function Section({section, def, parentRelation, sectionIsEditable, sectio </Item> </Box> ) - }, [section]) + }, [config?.showMeta, section]) const renderQuantity = useCallback(quantityDef => { const key = quantityDef.name @@ -1087,7 +1103,7 @@ export function Section({section, def, parentRelation, sectionIsEditable, sectio <Box marginTop={2}> {quantities .filter(filter) - .filter(quantityDef => !quantityDef.m_annotations?.eln) + .filter(quantityDef => !quantityDef.m_annotations?.eln?.[0]?.component) .map(renderQuantity) } </Box> @@ -1123,11 +1139,10 @@ export function Section({section, def, parentRelation, sectionIsEditable, sectio {isEditable && sectionIsInEln && ( <InheritingSections def={def} section={section} lane={lane}/> )} - <ArchiveTitle def={def} data={section} kindLabel="section" actions={actions}/> + <ArchiveTitle def={def} property={property} data={section} kindLabel="section" actions={actions}/> <Overview section={section} def={def}/> {contents} <ExternalReferences/> - <Meta def={def}/> </Content> ) } @@ -1135,6 +1150,7 @@ export function Section({section, def, parentRelation, sectionIsEditable, sectio Section.propTypes = ({ section: PropTypes.object.isRequired, def: PropTypes.object.isRequired, + property: PropTypes.object, parentRelation: PropTypes.object, sectionIsEditable: PropTypes.bool, sectionIsInEln: PropTypes.bool @@ -1155,6 +1171,7 @@ function SubSection({subSectionDef, section, editable}) { const lane = useLane() const history = useHistory() const [open, setOpen] = useState(false) + const config = useRecoilValue(configState) const {itemKey, label, getItemLabel} = useMemo(() => { const sectionDef = subSectionDef.sub_section @@ -1178,10 +1195,10 @@ function SubSection({subSectionDef, section, editable}) { } return { itemKey: subSectionDef.name, - label: formatSubSectionName(subSectionDef.name), + label: getDisplayLabel(subSectionDef, true, config?.showMeta), getItemLabel: getItemLabel } - }, [subSectionDef]) + }, [subSectionDef, config?.showMeta]) const handleAdd = useCallback(() => { let subSectionKey = subSectionDef.name @@ -1220,7 +1237,8 @@ function SubSection({subSectionDef, section, editable}) { if (showList) { return <PropertyValuesList itemKey={itemKey} - label={label || 'list'} actions={actions} + label={label || 'list'} + actions={actions} values={values.map(getItemLabel)} open={open} onClick={handleClick} @@ -1253,14 +1271,17 @@ function ReferenceValuesList({quantityDef}) { const lane = useContext(laneContext) const values = useMemo(() => lane.adaptor.obj[quantityDef.name].map(() => null), [lane.adaptor.obj, quantityDef.name]) const [open, setOpen] = useState(false) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const handleClick = useCallback(() => { setOpen(open => !open) }, []) return <PropertyValuesList - values={values} itemKey={quantityDef.name} + values={values} + label={label} open={open} onClick={handleClick} /> @@ -1597,7 +1618,6 @@ function Quantity({value, def, unit, children}) { /> </Compartment> {children} - <Meta def={def}/> <ExternalReferences/> </Content> } diff --git a/gui/src/components/archive/Browser.js b/gui/src/components/archive/Browser.js index 761875d41b..6f843f7e0a 100644 --- a/gui/src/components/archive/Browser.js +++ b/gui/src/components/archive/Browser.js @@ -27,6 +27,8 @@ import { useDataStore } from '../DataStore' import { useApi } from '../api' import NavigateIcon from '@material-ui/icons/ArrowRight' import H5Web from '../visualization/H5Web' +import {CopyToClipboard} from "react-copy-to-clipboard" +import ClipboardIcon from "@material-ui/icons/Assignment" function escapeBadPathChars(s) { return s.replace(/!/g, '!0').replace(/\?/g, '!1').replace(/#/g, '!2').replace(/%/g, '!3').replace(/\\/g, '!4') @@ -36,10 +38,6 @@ function unescapeBadPathChars(s) { return s.replace(/!4/g, '\\').replace(/!3/g, '%').replace(/!2/g, '#').replace(/!1/g, '?').replace(/!0/g, '!') } -export function formatSubSectionName(name) { - return name -} - /** * Browsers are made out of lanes. Each lane uses an adaptor that determines how to render * the lane contents and what adaptor is used for the next lane (depending on what is @@ -630,7 +628,52 @@ const useTitleStyles = makeStyles(theme => ({ } })) -export function Title({title, label, tooltip, actions, ...moreProps}) { +function LinkWithCopyToClipboard(props) { + const {label, name, itemKey, ...moreProps} = props + const lane = useLane() + const selected = lane?.next && lane?.next.key + const isSelected = itemKey && (selected === itemKey) + + return <Box display={'flex'} alignItems={'center'} marginBottom={1}> + {label && ( + <Typography color={moreProps.color}> + {label} + </Typography> + )} + {name && ( + <ItemLink itemKey={itemKey} style={{ textDecoration: 'none', marginLeft: 3 }}> + {isSelected + ? <Chip + label={name} + color="primary" + size="small" + /> + : <Typography color={'primary'}> + {name} + </Typography>} + </ItemLink> + )} + {name && ( + <CopyToClipboard + text={name} + onCopy={() => null} + > + <Tooltip title={`Copy "${name}" to clipboard`}> + <IconButton style={{ padding: 0, marginLeft: 3 }}> + <ClipboardIcon fontSize="small"/> + </IconButton> + </Tooltip> + </CopyToClipboard> + )} + </Box> +} +LinkWithCopyToClipboard.propTypes = ({ + label: PropTypes.string, + name: PropTypes.string, + itemKey: PropTypes.string +}) + +export function Title({title, label, definitionName, subSectionName, tooltip, actions, ...moreProps}) { const classes = useTitleStyles() return <Compartment> <Grid container justifyContent="space-between" wrap="nowrap" spacing={1}> @@ -642,11 +685,10 @@ export function Title({title, label, tooltip, actions, ...moreProps}) { ) : ( <Typography variant="h6" noWrap {...moreProps}>{title || <i>unnamed</i>}</Typography> )} - {label && ( - <Typography variant="caption" color={moreProps.color}> - {label} - </Typography> - )} + <Box display={'flex'} flexDirection={'column'}> + {definitionName ? <LinkWithCopyToClipboard label={label} name={definitionName} itemKey={'_metainfo'} {...moreProps}/> : <LinkWithCopyToClipboard label={label} {...moreProps}/>} + {subSectionName && <LinkWithCopyToClipboard label={'sub section'} name={subSectionName} itemKey={'_subsectionmetainfo'} {...moreProps}/>} + </Box> </Grid> {actions && ( <Grid item> @@ -659,6 +701,8 @@ export function Title({title, label, tooltip, actions, ...moreProps}) { Title.propTypes = ({ title: PropTypes.string, label: PropTypes.string, + definitionName: PropTypes.string, + subSectionName: PropTypes.string, tooltip: PropTypes.string, actions: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), diff --git a/gui/src/components/archive/MetainfoBrowser.js b/gui/src/components/archive/MetainfoBrowser.js index 0532adbd18..021dbf001c 100644 --- a/gui/src/components/archive/MetainfoBrowser.js +++ b/gui/src/components/archive/MetainfoBrowser.js @@ -19,7 +19,7 @@ import React, {useMemo, useEffect, useRef, useLayoutEffect, useContext, useState import PropTypes from 'prop-types' import { useRecoilValue, useRecoilState, atom } from 'recoil' import { configState } from './ArchiveBrowser' -import Browser, { Item, Content, Compartment, Adaptor, laneContext, formatSubSectionName, Title, ItemChip } from './Browser' +import Browser, { Item, Content, Compartment, Adaptor, laneContext, Title, ItemChip } from './Browser' import { Typography, Box, makeStyles, FormGroup, TextField, Button, Link } from '@material-ui/core' import { vicinityGraph, SubSectionMDef, SectionMDef, QuantityMDef, CategoryMDef, useGlobalMetainfo, PackageMDef, AttributeMDef, getMetainfoFromDefinition } from './metainfo' import * as d3 from 'd3' @@ -38,6 +38,7 @@ import { useErrors } from '../errors' import { SourceJsonDialogButton } from '../buttons/SourceDialogButton' import ReactJson from 'react-json-view' import ArchiveSearchBar from './ArchiveSearchBar' +import {getDisplayLabel} from "../../utils" export const help = ` The NOMAD *metainfo* defines all quantities used to represent archive data in @@ -520,7 +521,7 @@ function SectionDefContent({def, inheritingSections}) { return <Item key={key} itemKey={key}> <Typography component="span" color={unused && 'error'}> <Box fontWeight="bold" component="span"> - {formatSubSectionName(subSectionDef.more?.label || subSectionDef.name)} + {subSectionDef.name} </Box> {subSectionDef.repeats && <ItemChip label="repeats"/>} {subSectionDef._overwritten && <ItemChip label="overwritten" />} @@ -541,7 +542,7 @@ function SectionDefContent({def, inheritingSections}) { <Box component="span" whiteSpace="nowrap"> <Typography component="span" color={unused && 'error'}> <Box fontWeight="bold" component="span"> - {quantityDef.more?.label || quantityDef.name} + {quantityDef.name} </Box> </Typography> {quantityDef._overwritten && <ItemChip label="overwritten" />} @@ -561,7 +562,7 @@ function SectionDefContent({def, inheritingSections}) { <Box component="span" whiteSpace="nowrap"> <Typography component="span" color={unused && 'error'}> <Box fontWeight="bold" component="span"> - {innerSectionDef.more?.label || innerSectionDef.name} + {getDisplayLabel(innerSectionDef, true)} </Box> </Typography> </Box> @@ -580,8 +581,15 @@ SectionDefContent.propTypes = ({ }) function SectionDef({def, inheritingSections}) { + const label = getDisplayLabel(def, true) return <Content> <Definition def={def} kindLabel="section definition" /> + <DefinitionProperties def={def}> + <Typography> + <b>label</b>: + {label} + </Typography> + </DefinitionProperties> <SectionDefContent def={def} inheritingSections={inheritingSections}/> </Content> } @@ -592,9 +600,16 @@ SectionDef.propTypes = ({ function SubSectionDef({def, inheritingSections}) { const sectionDef = def.sub_section + const label = getDisplayLabel(def, true) return <React.Fragment> <Content> <ArchiveTitle def={def} useName isDefinition kindLabel="sub section definition" /> + <DefinitionProperties def={def}> + <Typography> + <b>label</b>: + {label} + </Typography> + </DefinitionProperties> <DefinitionDocs def={sectionDef} /> <Attributes def={def}/> <Annotations def={def}/> @@ -636,9 +651,14 @@ DefinitionProperties.propTypes = ({ }) function QuantityDef({def}) { + const label = getDisplayLabel(def, true) return <Content> <Definition def={def} kindLabel="quantity definition"/> <DefinitionProperties def={def}> + <Typography> + <b>label</b>: + {label} + </Typography> {def.type.type_kind !== 'reference' ? <Typography> <b>type</b>: @@ -843,17 +863,20 @@ const definitionLabels = { [AttributeMDef]: 'attribute' } -export function ArchiveTitle({def, isDefinition, data, kindLabel, useName, actions}) { +export function ArchiveTitle({def, property, isDefinition, data, kindLabel, useName, actions}) { const color = isDefinition ? 'primary' : 'initial' let label = definitionLabels[def.m_def] if (def.extends_base_section) { label += ' extension' } + const title = isDefinition || useName ? def.name : getDisplayLabel(def) return <Title - title={(!useName && def.more?.label) || def.label || def.name} + title={title} tooltip={def._qualifiedName || def.name} label={`${label}${isDefinition ? ' definition' : ''}`} color={color} + definitionName={!isDefinition && def.name} + subSectionName={!isDefinition && property && property.m_def === 'nomad.metainfo.metainfo.SubSection' && property.name} actions={actions || <SourceJsonDialogButton buttonProps={{size: 'small'}} @@ -866,6 +889,7 @@ export function ArchiveTitle({def, isDefinition, data, kindLabel, useName, actio } ArchiveTitle.propTypes = ({ def: PropTypes.object.isRequired, + property: PropTypes.object, data: PropTypes.any, isDefinition: PropTypes.bool, kindLabel: PropTypes.string, diff --git a/gui/src/components/conftest.spec.js b/gui/src/components/conftest.spec.js index 9596e0cecf..532394cbc1 100644 --- a/gui/src/components/conftest.spec.js +++ b/gui/src/components/conftest.spec.js @@ -46,6 +46,7 @@ import { defaultFilterData } from './search/FilterRegistry' import { keycloakBase, searchQuantities, ui } from '../config' import { useKeycloak } from '@react-keycloak/web' import { GlobalMetainfo } from './archive/metainfo' +import {capitalize} from "../utils" beforeEach(async () => { // For some strange reason, the useKeycloak mock gets reset if we set it earlier @@ -350,7 +351,7 @@ export function within(element, queriesToBind = defaultAndCustomQueries) { */ export function expectQuantity(name, data, label = undefined, description = undefined, root = customScreen) { description = description || searchQuantities[name].description - label = label || defaultFilterData?.[name]?.label?.toLowerCase() || searchQuantities[name].name.replace(/_/g, ' ') + label = label || defaultFilterData?.[name]?.label || capitalize(searchQuantities[name].name.replace(/_/g, ' ')) const value = isPlainObject(data) ? get(data, name) : data const element = root.getByTooltip(description) expect(root.getByText(label)).toBeInTheDocument() diff --git a/gui/src/components/datatable/Datatable.js b/gui/src/components/datatable/Datatable.js index 6f4c10f001..8ad3e5fbb7 100644 --- a/gui/src/components/datatable/Datatable.js +++ b/gui/src/components/datatable/Datatable.js @@ -25,6 +25,7 @@ import { IconButton, makeStyles, lighten, TableHead, TableRow, TableCell, TableS import TooltipButton from '../utils/TooltipButton' import EditColumnsIcon from '@material-ui/icons/ViewColumn' import InfiniteScroll from 'react-infinite-scroller' +import {capitalize} from "../../utils" const DatatableContext = React.createContext({}) const StaticDatatableContext = React.createContext({}) @@ -280,7 +281,7 @@ const DatatableHeader = React.memo(function DatatableHeader({actions}) { /> </TableCell>} {columns.map(column => { - const label = column.label || column.key + const label = column.label || capitalize(column.key) const description = column.description || '' const columnLabel = <Tooltip title={description} placement="top"> <span>{label}</span> @@ -645,7 +646,7 @@ const DatatableColumnSelector = React.memo(function DatatableColumnSelector({chi } return <React.Fragment> <TooltipButton - title="Change the shown columns." + title="Change the shown columns" component={IconButton} onClick={(event) => setAnchorEl(anchorEl ? null : event.currentTarget)} > @@ -663,6 +664,7 @@ const DatatableColumnSelector = React.memo(function DatatableColumnSelector({chi vertical: 'top', horizontal: 'center' }} + data-testid={'column-select-menu'} > <List> {columns.map(column => <ListItem diff --git a/gui/src/components/editQuantity/AuthorEditQuantity.js b/gui/src/components/editQuantity/AuthorEditQuantity.js index 2a1726d09b..5cc2a5a6f1 100644 --- a/gui/src/components/editQuantity/AuthorEditQuantity.js +++ b/gui/src/components/editQuantity/AuthorEditQuantity.js @@ -24,6 +24,9 @@ import {debounce} from 'lodash' import {fetchUsers} from '../uploads/EditMembersDialog' import {useApi} from '../api' import {useErrors} from '../errors' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" const useStyles = makeStyles(theme => ({ label: { @@ -48,6 +51,8 @@ export const AuthorEditQuantity = React.memo((props) => { const [suggestions, setSuggestions] = useState([]) const [searching, setSearching] = useState(false) const userOnly = useMemo(() => quantityDef.type?.type_kind === 'User', [quantityDef]) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const searchUsers = useCallback((value) => { const newQuery = value.toLowerCase() @@ -167,7 +172,7 @@ export const AuthorEditQuantity = React.memo((props) => { margin='normal' fullWidth {...getFieldProps(quantityDef)} - label={(userOnly ? getFieldProps(quantityDef).label : 'User account')} + label={(userOnly ? label : 'User account')} /> )} data-testid='user-edit-quantity' @@ -178,7 +183,7 @@ export const AuthorEditQuantity = React.memo((props) => { {userOnly ? userField : ( <Box marginTop={2} marginBottom={2}> <FormLabel component="legend"> - {getFieldProps(quantityDef).label} + {label} </FormLabel> {userField} <Typography className={classes.label} variant={'caption'}> diff --git a/gui/src/components/editQuantity/AutocompleteEditQuantity.js b/gui/src/components/editQuantity/AutocompleteEditQuantity.js index a6af20ce9a..f4a317a8b2 100644 --- a/gui/src/components/editQuantity/AutocompleteEditQuantity.js +++ b/gui/src/components/editQuantity/AutocompleteEditQuantity.js @@ -19,9 +19,14 @@ import React, {useCallback} from 'react' import PropTypes from 'prop-types' import AutoComplete from '@material-ui/lab/Autocomplete' import {getFieldProps, TextFieldWithHelp} from './StringEditQuantity' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" export const AutocompleteEditQuantity = React.memo((props) => { const {quantityDef, value, onChange, ...otherProps} = props + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const handleChange = useCallback((event, value) => { value = value || event.target.value @@ -41,6 +46,7 @@ export const AutocompleteEditQuantity = React.memo((props) => { variant='filled' size='small' fullWidth {...getFieldProps(quantityDef)} {...otherProps} + label={label} /> )} /> diff --git a/gui/src/components/editQuantity/BoolEditQuantity.js b/gui/src/components/editQuantity/BoolEditQuantity.js index 2d1086368a..d138234b68 100644 --- a/gui/src/components/editQuantity/BoolEditQuantity.js +++ b/gui/src/components/editQuantity/BoolEditQuantity.js @@ -22,10 +22,15 @@ import { } from '@material-ui/core' import PropTypes from 'prop-types' import {getFieldProps, WithHelp} from './StringEditQuantity' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" export const BoolEditQuantity = React.memo(function BoolEditQuantity(props) { const {quantityDef, value, onChange, ...otherProps} = props const fieldProps = getFieldProps(quantityDef) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const handleChange = useCallback((event) => { const value = event.target.checked @@ -34,7 +39,7 @@ export const BoolEditQuantity = React.memo(function BoolEditQuantity(props) { } }, [onChange]) - return <WithHelp {...fieldProps}> + return <WithHelp {...fieldProps} label={label}> <FormControlLabel control={( <Checkbox @@ -43,7 +48,7 @@ export const BoolEditQuantity = React.memo(function BoolEditQuantity(props) { checked={!!value} {...otherProps} />)} - label={fieldProps.label} + label={label} /> </WithHelp> }) diff --git a/gui/src/components/editQuantity/DateTimeEditQuantity.js b/gui/src/components/editQuantity/DateTimeEditQuantity.js index 1588b5b2ba..20b2068015 100644 --- a/gui/src/components/editQuantity/DateTimeEditQuantity.js +++ b/gui/src/components/editQuantity/DateTimeEditQuantity.js @@ -20,11 +20,15 @@ import PropTypes from 'prop-types' import {dateFormat} from '../../config' import {KeyboardDatePicker, KeyboardTimePicker} from '@material-ui/pickers' import AccessTimeIcon from '@material-ui/icons/AccessTime' -import {getFieldProps} from './StringEditQuantity' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" export const DateTimeEditQuantity = React.memo((props) => { const {quantityDef, value, onChange, format, time, ...otherProps} = props const [dateValue, setDateValue] = useState(value || null) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) useEffect(() => { setDateValue(date => value @@ -52,7 +56,7 @@ export const DateTimeEditQuantity = React.memo((props) => { variant: 'inline', inputVariant: 'filled', fullWidth: true, - label: getFieldProps(quantityDef).label, + label: label, value: dateValue, onChange: handleChange, ...otherProps diff --git a/gui/src/components/editQuantity/EnumEditQuantity.js b/gui/src/components/editQuantity/EnumEditQuantity.js index 5d155ec850..a44c739dcf 100644 --- a/gui/src/components/editQuantity/EnumEditQuantity.js +++ b/gui/src/components/editQuantity/EnumEditQuantity.js @@ -20,10 +20,15 @@ import {MenuItem} from '@material-ui/core' import PropTypes from 'prop-types' import {getFieldProps, TextFieldWithHelp} from './StringEditQuantity' import AutoComplete from '@material-ui/lab/Autocomplete' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" export const EnumEditQuantity = React.memo((props) => { const {quantityDef, value, onChange, suggestions, ...otherProps} = props const fieldProps = getFieldProps(quantityDef) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const handleChange = useCallback(value => { if (onChange) { @@ -45,6 +50,7 @@ export const EnumEditQuantity = React.memo((props) => { variant='filled' size='small' fullWidth {...fieldProps} {...otherProps} + label={label} /> )} /> diff --git a/gui/src/components/editQuantity/FileEditQuantity.js b/gui/src/components/editQuantity/FileEditQuantity.js index d2822085e7..9606bf1fdb 100644 --- a/gui/src/components/editQuantity/FileEditQuantity.js +++ b/gui/src/components/editQuantity/FileEditQuantity.js @@ -27,6 +27,9 @@ import {ItemButton, useLane} from '../archive/Browser' import { useEntryStore } from '../entry/EntryContext' import OverwriteExistingFileDialog from './OverwriteExistingFileDialog' import UploadProgressDialog from '../uploads/UploadProgressDialog' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" const useFileEditQuantityStyles = makeStyles(theme => ({ dropzone: { @@ -45,12 +48,14 @@ const FileEditQuantity = React.memo(props => { const classes = useFileEditQuantityStyles() const {onChange, onFailed, quantityDef, value, ...otherProps} = props const {index} = otherProps - const {uploadId, metadata} = useEntryStore() + const {uploadId, metadata} = useEntryStore() || {} const {api} = useApi() const [askForOverwrite, setAskForOverwrite] = useState(false) const dropedFiles = useRef([]) const [uploading, setUploading] = useState(null) const lane = useLane() + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const uploadFile = useCallback((files, overwrite = false) => { const mainfilePathSegments = metadata.mainfile.split('/') @@ -139,7 +144,7 @@ const FileEditQuantity = React.memo(props => { <TextField value={value || ''} onChange={handleChange} size="small" variant="filled" fullWidth - label={quantityDef.name} + label={label} {...otherProps} InputProps={{ endAdornment: ( diff --git a/gui/src/components/editQuantity/ListEditQuantity.js b/gui/src/components/editQuantity/ListEditQuantity.js index 87f4af3278..17d90b249a 100644 --- a/gui/src/components/editQuantity/ListEditQuantity.js +++ b/gui/src/components/editQuantity/ListEditQuantity.js @@ -18,8 +18,10 @@ import React, {useCallback, useState} from 'react' import PropTypes from 'prop-types' import {Box, makeStyles} from '@material-ui/core' -import {FoldableList} from '../archive/ArchiveBrowser' +import {configState, FoldableList} from '../archive/ArchiveBrowser' import grey from "@material-ui/core/colors/grey" +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" const useListEditQuantityStyles = makeStyles(theme => ({ root: { @@ -56,6 +58,8 @@ const ListEditQuantity = React.memo(function ListEditQuantity({value, onChange, const [open, setOpen] = useState(true) const fixedLength = Number(quantityDef.shape?.[0]) const hasFixedLength = !isNaN(fixedLength) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) let renderValue if (hasFixedLength) { @@ -94,7 +98,7 @@ const ListEditQuantity = React.memo(function ListEditQuantity({value, onChange, return <Box marginTop={1} marginBottom={1}> <FoldableList - label={quantityDef.name} + label={label} className={classes} open={open} onClick={handleClick} diff --git a/gui/src/components/editQuantity/NumberEditQuantity.js b/gui/src/components/editQuantity/NumberEditQuantity.js index 012f749fa4..5d9627766d 100644 --- a/gui/src/components/editQuantity/NumberEditQuantity.js +++ b/gui/src/components/editQuantity/NumberEditQuantity.js @@ -25,6 +25,9 @@ import {getUnits} from '../units/UnitContext' import {debounce, isNil} from 'lodash' import {TextFieldWithHelp, getFieldProps} from './StringEditQuantity' import {useDisplayUnit} from "../units/useDisplayUnit" +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" export const NumberField = React.memo((props) => { const {onChange, onInputChange, dimension, value, dataType, minValue, unit, maxValue, displayUnit, convertInPlace, debounceTime, ...otherProps} = props @@ -212,6 +215,8 @@ export const NumberEditQuantity = React.memo((props) => { const [displayedValue, setDisplayedValue] = useState(true) const {defaultDisplayUnit: deprecatedDefaultDisplayUnit, ...fieldProps} = getFieldProps(quantityDef) const displayUnit = useDisplayUnit(quantityDef) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const [unit, setUnit] = useState(displayUnit) // Set the unit if display unit changes @@ -260,6 +265,7 @@ export const NumberEditQuantity = React.memo((props) => { dataType={quantityDef.type?.type_data} {...fieldProps} {...otherProps} + label={label} /> {unit && ( <Box display='flex'> diff --git a/gui/src/components/editQuantity/RadioEnumEditQuantity.js b/gui/src/components/editQuantity/RadioEnumEditQuantity.js index 3b1d10d01b..971785fb22 100644 --- a/gui/src/components/editQuantity/RadioEnumEditQuantity.js +++ b/gui/src/components/editQuantity/RadioEnumEditQuantity.js @@ -23,10 +23,15 @@ import { } from '@material-ui/core' import PropTypes from 'prop-types' import {getFieldProps, WithHelp} from './StringEditQuantity' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" export const RadioEnumEditQuantity = React.memo((props) => { const {quantityDef, value, onChange, ...otherProps} = props const fieldProps = getFieldProps(quantityDef) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const handleChange = useCallback((value) => { if (onChange) { @@ -35,9 +40,9 @@ export const RadioEnumEditQuantity = React.memo((props) => { }, [onChange]) return ( - <WithHelp {...fieldProps}> + <WithHelp {...fieldProps} label={label}> <FormControl> - <FormLabel>{fieldProps.label}</FormLabel> + <FormLabel>{label}</FormLabel> <RadioGroup row> {quantityDef.type?.type_data.map(item => ( <FormControlLabel diff --git a/gui/src/components/editQuantity/ReferenceEditQuantity.js b/gui/src/components/editQuantity/ReferenceEditQuantity.js index 093ee5a2b5..81f3ff6c6e 100644 --- a/gui/src/components/editQuantity/ReferenceEditQuantity.js +++ b/gui/src/components/editQuantity/ReferenceEditQuantity.js @@ -32,7 +32,7 @@ import { import { useEntryStore } from '../entry/EntryContext' import {ItemButton, useLane} from '../archive/Browser' import { getFieldProps } from './StringEditQuantity' -import { refType, resolveNomadUrl } from '../../utils' +import { refType, resolveNomadUrl, getDisplayLabel } from '../../utils' import AddIcon from '@material-ui/icons/AddCircle' import { getUrlFromDefinition, QuantityMDef } from '../archive/metainfo' import EditIcon from '@material-ui/icons/Edit' @@ -44,6 +44,8 @@ import SectionSelectAutocomplete from '../uploads/SectionSelectAutocomplete' import {Link} from "react-router-dom" import DetailsIcon from '@material-ui/icons/ArrowForward' import OverwriteExistingFileDialog from './OverwriteExistingFileDialog' +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" const referenceEditQuantityContext = React.createContext(undefined) @@ -67,7 +69,7 @@ function useReferecedSectionDef(quantityDef) { const CreateNewReferenceDialog = React.memo(({allEntryDataSections, open, onSuccess, onFailed, onCanceled}) => { const classes = useStyles() - const {deploymentUrl, uploadId} = useEntryStore('*') + const {deploymentUrl, uploadId} = useEntryStore('*') || {} const {user, api} = useApi() const {raiseError} = useErrors() const [value, setValue] = useState('') @@ -244,7 +246,7 @@ ItemLink.propTypes = { } const ReferenceEditQuantity = React.memo(function ReferenceEditQuantity(props) { - const {url} = useEntryStore('*') + const {url} = useEntryStore('*') || {} const {quantityDef, value, onChange, index} = props const [entry, setEntry] = useState(null) const [open, setOpen] = useState(false) @@ -342,6 +344,8 @@ const ReferenceEditQuantity = React.memo(function ReferenceEditQuantity(props) { }, [quantityDef, index]) const {helpDescription, showSectionLabel, ...otherProps} = getFieldProps(quantityDef) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const handleSuccess = useCallback((value) => { setCreateEntryDialogOpen(false) @@ -445,6 +449,7 @@ const ReferenceEditQuantity = React.memo(function ReferenceEditQuantity(props) { endAdornment: addIconButtonToEndAdornment(params.InputProps.endAdornment, actions) }} {...otherProps} + label={label} placeholder={'search by entry name or file name'} data-testid='reference-edit-quantity' /> diff --git a/gui/src/components/editQuantity/RichTextEditQuantity.js b/gui/src/components/editQuantity/RichTextEditQuantity.js index f046422505..bee04fae6e 100644 --- a/gui/src/components/editQuantity/RichTextEditQuantity.js +++ b/gui/src/components/editQuantity/RichTextEditQuantity.js @@ -19,8 +19,10 @@ import React, { useCallback, useState, useRef } from 'react' import { Editor } from '@tinymce/tinymce-react' import PropTypes from 'prop-types' import { Box, FormControl, FormLabel, makeStyles } from '@material-ui/core' -import { getFieldProps } from './StringEditQuantity' import DOMPurify from 'dompurify' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" const useStyle = makeStyles(theme => ({ root: { @@ -41,7 +43,8 @@ const RichTextEditQuantity = React.memo((props) => { const classes = useStyle() const {quantityDef, value, onChange, height} = props const initialHeight = height || 500 - const {label} = getFieldProps(quantityDef) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const initialValue = useRef(value) const editedValue = useRef(value) const [focus, setFocus] = useState(false) diff --git a/gui/src/components/editQuantity/SliderEditQuantity.js b/gui/src/components/editQuantity/SliderEditQuantity.js index 4c6b9c4bdb..e5375c568b 100644 --- a/gui/src/components/editQuantity/SliderEditQuantity.js +++ b/gui/src/components/editQuantity/SliderEditQuantity.js @@ -26,11 +26,14 @@ import {Quantity} from '../units/Quantity' import {Unit} from '../units/Unit' import {useUnitContext} from '../units/UnitContext' import {UnitSelect} from './NumberEditQuantity' -import {getFieldProps} from './StringEditQuantity' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" export const SliderEditQuantity = React.memo((props) => { const {quantityDef, value, onChange, minValue, maxValue, ...sliderProps} = props - const {label} = getFieldProps(quantityDef) + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const {units} = useUnitContext() const defaultUnit = useMemo(() => quantityDef.unit && new Unit(quantityDef.unit), [quantityDef]) diff --git a/gui/src/components/editQuantity/StringEditQuantity.js b/gui/src/components/editQuantity/StringEditQuantity.js index 28d3bdf3b9..223e9cb486 100644 --- a/gui/src/components/editQuantity/StringEditQuantity.js +++ b/gui/src/components/editQuantity/StringEditQuantity.js @@ -32,6 +32,9 @@ import Markdown from '../Markdown' import DialogActions from '@material-ui/core/DialogActions' import Button from '@material-ui/core/Button' import LaunchIcon from '@material-ui/icons/Launch' +import {getDisplayLabel} from "../../utils" +import {useRecoilValue} from "recoil" +import {configState} from "../archive/ArchiveBrowser" const HelpDialog = React.memo(({title, description}) => { const [open, setOpen] = useState(false) @@ -114,19 +117,11 @@ WithHelp.propTypes = { helpDescription: PropTypes.string } -const capitalize = (s) => { - if (typeof s !== 'string') return '' - return s.charAt(0).toUpperCase() + s.slice(1) -} - export function getFieldProps(quantityDef) { const eln = quantityDef?.m_annotations?.eln - const name = quantityDef.name.replace(/_/g, ' ') - const label = eln?.[0].label || capitalize(name) const {component, ...elnProps} = eln?.[0] || {} elnProps.unit = elnProps.unit || quantityDef.unit return { - label: label, helpDescription: quantityDef.description, ...elnProps } @@ -159,6 +154,8 @@ TextFieldWithHelp.propTypes = { export const StringEditQuantity = React.memo((props) => { const {quantityDef, onChange, ...otherProps} = props + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const handleChange = useCallback((value) => { if (onChange) { @@ -170,6 +167,7 @@ export const StringEditQuantity = React.memo((props) => { onChange={event => handleChange(event.target.value)} {...getFieldProps(quantityDef)} {...otherProps} + label={label} /> }) StringEditQuantity.propTypes = { @@ -246,6 +244,8 @@ TextFieldWithLinkButton.propTypes = { export const URLEditQuantity = React.memo((props) => { const {quantityDef, onChange, ...otherProps} = props + const config = useRecoilValue(configState) + const label = getDisplayLabel(quantityDef, true, config?.showMeta) const handleChange = useCallback((value) => { if (onChange) { @@ -258,6 +258,7 @@ export const URLEditQuantity = React.memo((props) => { onChange={event => handleChange(event.target.value)} {...getFieldProps(quantityDef)} {...otherProps} + label={label} /> }) URLEditQuantity.propTypes = { diff --git a/gui/src/components/editQuantity/display.spec.js b/gui/src/components/editQuantity/display.spec.js new file mode 100644 index 0000000000..da2bfadcf0 --- /dev/null +++ b/gui/src/components/editQuantity/display.spec.js @@ -0,0 +1,247 @@ +/* + * 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 from 'react' +import {render, screen} from "../conftest.spec" +import {editQuantityComponents} from "./EditQuantity" +import {PackageMDef, QuantityMDef} from "../archive/metainfo" +import {StringEditQuantity} from "./StringEditQuantity" +import ListEditQuantity from "./ListEditQuantity" + +test.each([ + [ + 'NumberEditQuantity without custom label', + 'NumberEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'NumberEditQuantity with custom label', + 'NumberEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'StringEditQuantity without custom label', + 'StringEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'StringEditQuantity with custom label', + 'StringEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'URLEditQuantity without custom label', + 'URLEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'URLEditQuantity with custom label', + 'URLEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'EnumEditQuantity without custom label', + 'EnumEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'EnumEditQuantity with custom label', + 'EnumEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'SelectEnumEditQuantity without custom label', + 'SelectEnumEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'SelectEnumEditQuantity with custom label', + 'SelectEnumEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'RadioEnumEditQuantity without custom label', + 'RadioEnumEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'RadioEnumEditQuantity with custom label', + 'RadioEnumEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'AutocompleteEditQuantity without custom label', + 'AutocompleteEditQuantity', + {type: {type_kind: 'Enum', type_data: []}}, + undefined, + 'my value' + ], + [ + 'AutocompleteEditQuantity with custom label', + 'AutocompleteEditQuantity', + {type: {type_kind: 'Enum', type_data: []}}, + 'My custom value', + 'My custom value' + ], + [ + 'BoolEditQuantity without custom label', + 'BoolEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'BoolEditQuantity with custom label', + 'BoolEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'FileEditQuantity without custom label', + 'FileEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'FileEditQuantity with custom label', + 'FileEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'DateTimeEditQuantity without custom label', + 'DateTimeEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'DateTimeEditQuantity with custom label', + 'DateTimeEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'RichTextEditQuantity without custom label', + 'RichTextEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'RichTextEditQuantity with custom label', + 'RichTextEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ], + [ + 'ReferenceEditQuantity without custom label', + 'ReferenceEditQuantity', + {type: {_referencedDefinition: {m_def: QuantityMDef, _section: {m_def: PackageMDef, _allBaseSections: [], _qualifiedName: 'a', _pkgParentData: {_metainfo: {_parsedUrl: 'a'}}}}}}, + undefined, + 'my value' + ], + [ + 'ReferenceEditQuantity with custom label', + 'ReferenceEditQuantity', + {type: {_referencedDefinition: {m_def: QuantityMDef, _section: {m_def: PackageMDef, _allBaseSections: [], _qualifiedName: 'a', _pkgParentData: {_metainfo: {_parsedUrl: 'a'}}}}}}, + 'My custom value', + 'My custom value' + ], + [ + 'AuthorEditQuantity without custom label', + 'AuthorEditQuantity', + undefined, + undefined, + 'my value' + ], + [ + 'AuthorEditQuantity with custom label', + 'AuthorEditQuantity', + undefined, + 'My custom value', + 'My custom value' + ] +])('Test label of %s', async (name, component, def, customLabel, expected) => { + render(React.createElement( + editQuantityComponents[component], { + quantityDef: { + name: 'my_value', + m_annotations: {display: [{label: customLabel}]}, + ...def + }, + type: {type_kind: 'python', type_data: 'string'}, + value: undefined + } + )) + screen.getByText(expected) +}) + +test.each([ + [ + 'without custom label', + undefined, + 'my value' + ], + [ + 'with custom label', + 'My custom value', + 'My custom value' + ] +])('Test label of ListEditQuantity %s', async (name, customLabel, expected) => { + render( + <ListEditQuantity + component={StringEditQuantity} + quantityDef={{ + name: 'my_value', + m_annotations: {display: [{label: customLabel}]} + }} + type={{type_kind: 'python', type_data: 'string'}} + />) + const labels = screen.getAllByText(expected) + expect(labels.length).toBe(2) +}) diff --git a/gui/src/components/entry/EntryDetails.js b/gui/src/components/entry/EntryDetails.js index 9344f7ff75..70a0e4fb11 100644 --- a/gui/src/components/entry/EntryDetails.js +++ b/gui/src/components/entry/EntryDetails.js @@ -51,8 +51,8 @@ export const MethodMetadata = React.memo(({data}) => { addMethodQuantities(data.results.eln, 'results.eln', ['names', 'sections', 'descriptions']) } - methodQuantities.push({quantity: 'entry_type', label: 'type'}) - methodQuantities.push({quantity: 'entry_name', label: 'name'}) + methodQuantities.push({quantity: 'entry_type'}) + methodQuantities.push({quantity: 'entry_name'}) return <Quantity flex> {methodQuantities.map(({...quantityProps}) => ( diff --git a/gui/src/components/entry/OverviewView.spec.js b/gui/src/components/entry/OverviewView.spec.js index 69a6f92e9e..c8b10419fd 100644 --- a/gui/src/components/entry/OverviewView.spec.js +++ b/gui/src/components/entry/OverviewView.spec.js @@ -91,14 +91,15 @@ test('correctly renders metadata and all properties', async () => { // Check if all metadata is shown (on the left) expectQuantity('results.method.method_name', index) - expectQuantity('comment', index) - expectQuantity('references', index.references[0]) - expectQuantity('authors', 'Markus Scheidgen') expectQuantity('mainfile', index) expectQuantity('entry_id', index) expectQuantity('upload_id', index) expectQuantity('results.material.material_id', index) - expectQuantity(undefined, `${index.nomad_version}/${index.nomad_commit}`, 'processing version', 'Version used in the last processing') + expectQuantity(undefined, `${index.nomad_version}/${index.nomad_commit}`, 'Processing version', 'Version used in the last processing') + // test the quantities with presets label + expectQuantity('comment', index) + expectQuantity('references', index.references[0]) + expectQuantity('authors', 'Markus Scheidgen') // TODO: add the following to the state for testing. // expectQuantity('datasets', index.datasets[0].dataset_name) // expectQuantity('upload_create_time', formatTimestamp(index.upload_create_time)) @@ -209,16 +210,17 @@ test('eln overview as a reviewer', async () => { expect(within(cardSample).getByText('Sample')).toBeVisible() // expectQuantityToBe('chemical_formula', 'chemical formula', undefined, within(cardSample)) - expectQuantityToBe('name', 'name', 'ELN example sample', within(cardSample)) - expectQuantityToBe('lab_id', 'lab id', '001', within(cardSample)) - expectQuantityToBe('description', 'description', undefined, within(cardSample)) - expectQuantityToBe('tags', 'tags', 'project', within(cardSample)) - expectQuantityToBe('substrate_type', 'substrate type', 'SLG', within(cardSample)) - expectQuantityToBe('substrate_thickness', 'substrate thickness', undefined, within(cardSample)) - expectQuantityToBe('sample_is_from_collaboration', 'sample is from collaboration', undefined, within(cardSample)) + // test the quantities with deprecated eln annotation label + expectQuantityToBe('name', 'Name', 'ELN example sample', within(cardSample)) + expectQuantityToBe('lab_id', 'ID', '001', within(cardSample)) + expectQuantityToBe('description', 'Description', undefined, within(cardSample)) + expectQuantityToBe('tags', 'Tags', 'project', within(cardSample)) + expectQuantityToBe('substrate_type', 'Substrate type', 'SLG', within(cardSample)) + expectQuantityToBe('substrate_thickness', 'Substrate thickness', undefined, within(cardSample)) + expectQuantityToBe('sample_is_from_collaboration', 'Sample is from collaboration', undefined, within(cardSample)) expect(within(cardPvdEvaporation).getByText('PvdEvaporation')).toBeVisible() - expectQuantityToBe('data_file', 'data file', 'PVDProcess.csv', within(cardPvdEvaporation)) + expectQuantityToBe('data_file', 'Data file', 'PVDProcess.csv', within(cardPvdEvaporation)) // Test if the plot is there expect(within(cardPvdEvaporation).getByText(/Chamber Pressure \(GPa\)/)).toBeVisible() @@ -226,10 +228,10 @@ test('eln overview as a reviewer', async () => { expect(within(cardPvdEvaporation).getByText(/Time \(fs\)/)).toBeVisible() expect(within(cardHotplateAnnealing).getByText('HotplateAnnealing')).toBeVisible() - expectQuantityToBe('instrument', 'instrument', undefined, within(cardHotplateAnnealing)) - expectQuantityToBe('method', 'method', undefined, within(cardHotplateAnnealing)) - expectQuantityToBe('set_temperature', 'set temperature', '373.15', within(cardHotplateAnnealing)) - expectQuantityToBe('duration', 'duration', '60', within(cardHotplateAnnealing)) + expectQuantityToBe('instrument', 'Instrument', undefined, within(cardHotplateAnnealing)) + expectQuantityToBe('method', 'Method', undefined, within(cardHotplateAnnealing)) + expectQuantityToBe('set_temperature', 'Set temperature', '373.15', within(cardHotplateAnnealing)) + expectQuantityToBe('duration', 'Duration', '60', within(cardHotplateAnnealing)) // Wait for the last API calls (e.g. reference card) to finish being recorded. await waitForGUI(5000) diff --git a/gui/src/components/entry/conftest.spec.js b/gui/src/components/entry/conftest.spec.js index ff1d62ca5b..df0b4a70ab 100644 --- a/gui/src/components/entry/conftest.spec.js +++ b/gui/src/components/entry/conftest.spec.js @@ -27,10 +27,10 @@ import { ui } from '../../config' /*****************************************************************************/ // Expects export function expectComposition(index) { - expectQuantity(undefined, index.results.material.chemical_formula_hill, 'formula', 'The chemical formula that describes the simulated system or experimental sample.') + expectQuantity(undefined, index.results.material.chemical_formula_hill, 'Formula', 'The chemical formula that describes the simulated system or experimental sample.') expectQuantity('results.material.structural_type', index) expectQuantity('results.material.elements', index) - expectQuantity('results.material.n_elements', '1 (unary)', 'number of elements') + expectQuantity('results.material.n_elements', '1 (unary)', 'N elements') } export function expectSymmetry(index) { diff --git a/gui/src/components/entry/properties/MaterialCard.js b/gui/src/components/entry/properties/MaterialCard.js index fd11155141..087a5c245c 100644 --- a/gui/src/components/entry/properties/MaterialCard.js +++ b/gui/src/components/entry/properties/MaterialCard.js @@ -41,7 +41,7 @@ export function Formula({data}) { } return <Quantity - quantity={formula} label='formula' noWrap data={data} + quantity={formula} label='Formula' noWrap data={data} description="The chemical formula that describes the simulated system or experimental sample." /> } diff --git a/gui/src/components/entry/properties/MaterialCardTopology.js b/gui/src/components/entry/properties/MaterialCardTopology.js index aae34adee5..7f58cd2f60 100644 --- a/gui/src/components/entry/properties/MaterialCardTopology.js +++ b/gui/src/components/entry/properties/MaterialCardTopology.js @@ -381,7 +381,6 @@ const MaterialTabs = React.memo(({value, onChange, node}) => { <QuantityRow> <QuantityCell value={node?.elements} quantity="results.material.topology.elements"/> <QuantityCell - label="number of elements" quantity="results.material.topology.n_elements" value={n_elements} format={false} @@ -492,7 +491,7 @@ const MaterialTabs = React.memo(({value, onChange, node}) => { <QuantityCell value={node?.active_orbitals?.ms_quantum_symbol} quantity="results.material.topology.active_orbitals.ms_quantum_symbol" - label="spin" + label="Spin" hideIfUnavailable={true} /> </QuantityRow> diff --git a/gui/src/components/search/Filter.js b/gui/src/components/search/Filter.js index efd69fd370..fd7f917c5e 100644 --- a/gui/src/components/search/Filter.js +++ b/gui/src/components/search/Filter.js @@ -21,7 +21,7 @@ import { getDatatype, getSerializer, getDeserializer, - formatLabel, + getDisplayLabel, DType, multiTypes } from '../../utils' @@ -47,7 +47,6 @@ export class Filter { dimension deserializer label - labelFull nested aggregatable section @@ -74,7 +73,6 @@ export class Filter { * from the metainfo. * - quantity: The quantity that this filter targets in the metainfo. * - schema: The schema in which the filter quantity is defined in - * - labelFull: Long name displayed for this filter. * - placeholder: Placeholder displayed for this filter in input fields. * - multiple: Whether the user can simultaneously provide multiple values for * this filter. @@ -121,6 +119,7 @@ export class Filter { * @param {Filter} parent Optional parent filter */ constructor(def, params, parent) { + this.def = def this.name = params?.name || def?.name this.quantity = params?.quantity || def?.quantity this.schema = params?.schema || def?.schema @@ -139,17 +138,7 @@ export class Filter { this.description = params?.description || def?.description this.unit = params?.unit || def?.unit this.dimension = def?.unit ? new Unit(def?.unit).dimension() : 'dimensionless' - this.label = params?.label || formatLabel(this.name) - let parentName - if (parent) { - const sections = this.quantity.split('.') - const nSections = sections.length - if (sections.length > 1) { - parentName = formatLabel(sections[nSections - 2]) - } - } - this.labelFull = parentName ? `${parentName} ${this.label}` : this.label - + this.label = params?.label || getDisplayLabel(def) this.parent = parent this.group = params.group this.placeholder = params?.placeholder diff --git a/gui/src/components/search/FilterRegistry.js b/gui/src/components/search/FilterRegistry.js index d2d9c7c1c3..efe6204984 100644 --- a/gui/src/components/search/FilterRegistry.js +++ b/gui/src/components/search/FilterRegistry.js @@ -99,6 +99,9 @@ function addToGroup(groups, groupName, quantityName) { * @param {obj} config Data object containing options for the filter. */ function saveFilter(name, group, config, parent) { + if (defaultFilterData[name]) { + throw Error(`Trying to register filter "${name}"" multiple times.`) + } const def = searchQuantities[name] const {path: quantity, schema} = parseQuantityName(name) const newConf = {...(config || {}), quantity, schema, name: config?.name || def?.name || name, aggregatable: def?.aggregatable, group: group} @@ -201,10 +204,10 @@ registerFilter('results.material.functional_type', idStructure, termQuantityNonE registerFilter('results.material.compound_type', idStructure, termQuantityNonExclusive) registerFilter('results.material.material_name', idStructure, termQuantity) registerFilter('results.material.chemical_formula_hill', idElements, {...termQuantity, placeholder: "E.g. H2O2, C2H5Br"}) -registerFilter('results.material.chemical_formula_iupac', idElements, {...termQuantity, placeholder: "E.g. GaAs, SiC", label: 'Chemical Formula IUPAC'}) +registerFilter('results.material.chemical_formula_iupac', idElements, {...termQuantity, placeholder: "E.g. GaAs, SiC", label: 'Chemical formula IUPAC'}) registerFilter('results.material.chemical_formula_reduced', idElements, {...termQuantity, placeholder: "E.g. H2NaO, ClNa"}) registerFilter('results.material.chemical_formula_anonymous', idElements, {...termQuantity, placeholder: "E.g. A2B, A3B2C2"}) -registerFilter('results.material.n_elements', idElements, {...numberHistogramQuantity, label: 'Number of Elements'}) +registerFilter('results.material.n_elements', idElements, {...numberHistogramQuantity}) registerFilter('results.material.symmetry.bravais_lattice', idStructure, termQuantity) registerFilter('results.material.symmetry.crystal_system', idStructure, termQuantity) registerFilter( @@ -299,13 +302,13 @@ registerFilter('results.method.workflow_name', idMethod, {...termQuantity, scale registerFilter('results.method.simulation.program_name', idMethod, {...termQuantity, scale: '1/4'}) registerFilter('results.method.simulation.program_version', idMethod, termQuantity) registerFilter('results.method.simulation.program_version_internal', idMethod, termQuantity) -registerFilter('results.method.simulation.precision.native_tier', idPrecision, {...termQuantity, placeholder: "E.g. VASP - accurate", label: 'Code-specific Tier'}) -registerFilter('results.method.simulation.precision.k_line_density', idPrecision, {...numberHistogramQuantity, scale: '1/2'}) +registerFilter('results.method.simulation.precision.native_tier', idPrecision, {...termQuantity, placeholder: "E.g. VASP - accurate", label: 'Code-specific tier'}) +registerFilter('results.method.simulation.precision.k_line_density', idPrecision, {...numberHistogramQuantity, scale: '1/2', label: 'k-line density'}) registerFilter('results.method.simulation.precision.basis_set', idPrecision, {...termQuantity, scale: '1/4'}) -registerFilter('results.method.simulation.precision.planewave_cutoff', idPrecision, {...numberHistogramQuantity, label: 'Plane-wave Cutoff', scale: '1/2'}) -registerFilter('results.method.simulation.precision.apw_cutoff', idPrecision, {...numberHistogramQuantity, label: 'APW Cutoff', scale: '1/2'}) +registerFilter('results.method.simulation.precision.planewave_cutoff', idPrecision, {...numberHistogramQuantity, label: 'Plane-wave cutoff', scale: '1/2'}) +registerFilter('results.method.simulation.precision.apw_cutoff', idPrecision, {...numberHistogramQuantity, label: 'APW cutoff', scale: '1/2'}) registerFilter('results.method.simulation.dft.core_electron_treatment', idDFT, termQuantity) -registerFilter('results.method.simulation.dft.jacobs_ladder', idDFT, {...termQuantity, scale: '1/2'}) +registerFilter('results.method.simulation.dft.jacobs_ladder', idDFT, {...termQuantity, scale: '1/2', label: 'Jacob\'s ladder'}) registerFilter('results.method.simulation.dft.xc_functional_type', idDFT, { ...termQuantity, scale: '1/2', @@ -318,13 +321,13 @@ registerFilter('results.method.simulation.dft.xc_functional_type', idDFT, { 'hybrid': {label: 'Hybrid'} } }) -registerFilter('results.method.simulation.dft.xc_functional_names', idDFT, {...termQuantityNonExclusive, scale: '1/2', label: 'XC Functional Names'}) +registerFilter('results.method.simulation.dft.xc_functional_names', idDFT, {...termQuantityNonExclusive, scale: '1/2', label: 'XC functional names'}) registerFilter('results.method.simulation.dft.exact_exchange_mixing_factor', idDFT, {...numberHistogramQuantity, scale: '1/2'}) registerFilter('results.method.simulation.dft.hubbard_kanamori_model.u_effective', idDFT, {...numberHistogramQuantity, scale: '1/2'}) registerFilter('results.method.simulation.dft.relativity_method', idDFT, termQuantity) registerFilter('results.method.simulation.tb.type', idTB, {...termQuantity, scale: '1/2'}) registerFilter('results.method.simulation.tb.localization_type', idTB, {...termQuantity, scale: '1/2'}) -registerFilter('results.method.simulation.gw.type', idGW, {...termQuantity, label: 'GW Type'}) +registerFilter('results.method.simulation.gw.type', idGW, {...termQuantity, label: 'GW type'}) registerFilter('results.method.simulation.gw.starting_point_type', idGW, { ...termQuantity, scale: '1/2', @@ -353,26 +356,25 @@ registerFilter('results.method.simulation.bse.starting_point_type', idBSE, { } }) registerFilter('results.method.simulation.bse.basis_set_type', idBSE, {...termQuantity, scale: '1/4'}) -registerFilter('results.method.simulation.bse.gw_type', idBSE, {...termQuantity, scale: '1/4', label: `GW Type`}) +registerFilter('results.method.simulation.bse.gw_type', idBSE, {...termQuantity, scale: '1/4', label: `GW type`}) registerFilter('results.method.simulation.dmft.impurity_solver_type', idDMFT, {...termQuantity}) registerFilter('results.method.simulation.dmft.magnetic_state', idDMFT, {...termQuantity}) registerFilter('results.method.simulation.dmft.inverse_temperature', idDMFT, {...numberHistogramQuantity, scale: '1/2'}) registerFilter('results.method.simulation.dmft.u', idDMFT, {...numberHistogramQuantity, scale: '1/2'}) registerFilter('results.method.simulation.dmft.jh', idDMFT, {...numberHistogramQuantity, label: `JH`, scale: '1/2'}) registerFilter('results.method.simulation.dmft.analytical_continuation', idDMFT, {...termQuantity}) -registerFilter('results.method.simulation.precision.k_line_density', idPrecision, {...termQuantity, label: 'k-line Density'}) registerFilter('results.eln.sections', idELN, termQuantity) registerFilter('results.eln.tags', idELN, termQuantity) registerFilter('results.eln.methods', idELN, termQuantity) registerFilter('results.eln.instruments', idELN, termQuantity) -registerFilter('results.eln.lab_ids', idELN, termQuantity) +registerFilter('results.eln.lab_ids', idELN, {...termQuantity, label: 'Lab IDs'}) registerFilter('results.eln.names', idELN, noAggQuantity) registerFilter('results.eln.descriptions', idELN, noAggQuantity) -registerFilter('external_db', idAuthor, {...termQuantity, label: 'External Database', scale: '1/4'}) -registerFilter('authors.name', idAuthor, {...termQuantityNonExclusive, label: 'Author Name'}) +registerFilter('external_db', idAuthor, {...termQuantity, label: 'External database', scale: '1/4'}) +registerFilter('authors.name', idAuthor, {...termQuantityNonExclusive, label: 'Author name'}) registerFilter('upload_create_time', idAuthor, {...numberHistogramQuantity, scale: '1/2'}) registerFilter('entry_create_time', idAuthor, {...numberHistogramQuantity, scale: '1/2'}) -registerFilter('datasets.dataset_name', idAuthor, {...termQuantityLarge, label: 'Dataset Name'}) +registerFilter('datasets.dataset_name', idAuthor, {...termQuantityLarge, label: 'Dataset name'}) registerFilter('datasets.doi', idAuthor, {...termQuantity, label: 'Dataset DOI'}) registerFilter('datasets.dataset_id', idAuthor, termQuantity) registerFilter('domain', idMetadata, termQuantity) @@ -386,7 +388,7 @@ registerFilter('main_author.user_id', idMetadata, termQuantity) registerFilter('quantities', idMetadata, {...noAggQuantity, label: 'Metainfo definition', queryMode: 'all'}) registerFilter('sections', idMetadata, {...noAggQuantity, label: 'Metainfo sections', queryMode: 'all'}) registerFilter('section_defs.definition_qualified_name', idMetadata, {...noAggQuantity, label: 'Section defs qualified name', queryMode: 'all'}) -registerFilter('entry_references.target_entry_id', idMetadata, {...noAggQuantity, label: 'Entry References Target entry id', queryMode: 'all'}) +registerFilter('entry_references.target_entry_id', idMetadata, {...noAggQuantity, label: 'Entry references target entry id', queryMode: 'all'}) registerFilter('entry_type', idMetadata, {...noAggQuantity, label: 'Entry type', queryMode: 'all'}) registerFilter('entry_name.prefix', idMetadata, {...noAggQuantity, label: 'Entry name', queryMode: 'all'}) registerFilter('results.material.material_id', idMetadata, termQuantity) @@ -422,7 +424,7 @@ registerFilter('custom_quantities', idCustomQuantities, { registerFilter( 'results.properties.spectroscopic.spectra.provenance.eels', idSpectroscopic, - {...nestedQuantity, label: 'Electron Energy Loss Spectrum (EELS)'}, + {...nestedQuantity, label: 'Electron energy loss spectrum (EELS)'}, [ {name: 'detector_type', ...termQuantity}, {name: 'resolution', ...numberHistogramQuantity}, @@ -433,7 +435,7 @@ registerFilter( registerFilter( 'results.properties.electronic.band_structure_electronic', idElectronic, - {...nestedQuantity, label: 'Band Structure'}, + {...nestedQuantity, label: 'Band structure'}, [ {name: 'spin_polarized', label: 'Spin-polarized', ...termQuantityBool} ] @@ -441,7 +443,7 @@ registerFilter( registerFilter( 'results.properties.electronic.dos_electronic', idElectronic, - {...nestedQuantity, label: 'Density of States'}, + {...nestedQuantity, label: 'Density of states'}, [ {name: 'spin_polarized', label: 'Spin-polarized', ...termQuantityBool} ] @@ -594,8 +596,8 @@ registerFilter( nestedQuantity, [ {name: 'available_properties', ...termQuantityAll}, - {name: 'provenance.molecular_dynamics.ensemble_type', ...termQuantity, label: 'Ensemble Type'}, - {name: 'provenance.molecular_dynamics.time_step', ...numberHistogramQuantity, label: 'Time Step'} + {name: 'provenance.molecular_dynamics.ensemble_type', ...termQuantity}, + {name: 'provenance.molecular_dynamics.time_step', ...numberHistogramQuantity} ] ) @@ -664,7 +666,7 @@ registerFilterOptions( 'electronic_properties', idElectronic, 'results.properties.available_properties', - 'Electronic Properties', + 'Electronic properties', 'The electronic properties that are present in an entry.', { 'electronic.band_structure_electronic.band_gap': {label: 'Band gap'}, @@ -680,7 +682,7 @@ registerFilterOptions( 'vibrational_properties', idVibrational, 'results.properties.available_properties', - 'Vibrational Properties', + 'Vibrational properties', 'The vibrational properties that are present in an entry.', { dos_phonon: {label: 'Phonon density of states'}, @@ -695,7 +697,7 @@ registerFilterOptions( 'mechanical_properties', idMechanical, 'results.properties.available_properties', - 'Mechanical Properties', + 'Mechanical properties', 'The mechanical properties that are present in an entry.', { bulk_modulus: {label: 'Bulk modulus'}, @@ -709,7 +711,7 @@ registerFilterOptions( 'spectroscopic_properties', idSpectroscopic, 'results.properties.available_properties', - 'Spectroscopic Properties', + 'Spectroscopic properties', 'The spectroscopic properties that are present in an entry.', { eels: {label: 'Electron energy loss spectrum'} @@ -721,7 +723,7 @@ registerFilterOptions( 'thermodynamic_properties', idMolecularDynamics, 'results.properties.available_properties', - 'Thermodynamic Properties', + 'Thermodynamic properties', 'The thermodynamic properties that are present.', { trajectory: {label: 'Trajectory'} diff --git a/gui/src/components/search/FilterSummary.spec.js b/gui/src/components/search/FilterSummary.spec.js index e64559f3ef..db890b76e0 100644 --- a/gui/src/components/search/FilterSummary.spec.js +++ b/gui/src/components/search/FilterSummary.spec.js @@ -22,10 +22,10 @@ import FilterSummary from './FilterSummary' import { SearchContext } from './SearchContext' test.each([ - ['integer', 'results.material.n_elements', 12, 'Number Of Elements', 'n_elements=12'], - ['string', 'results.material.symmetry.crystal_system', 'cubic', 'Crystal System', 'cubic'], - ['float', 'results.method.simulation.precision.k_line_density', 12.3, 'K Line Density (Ã…)', 'k_line_density=12.3'], - ['datetime', 'upload_create_time', 0, 'Upload Create Time', 'upload_create_time=01/01/1970'], + ['integer', 'results.material.n_elements', 12, 'N elements', 'n_elements=12'], + ['string', 'results.material.symmetry.crystal_system', 'cubic', 'Crystal system', 'cubic'], + ['float', 'results.method.simulation.precision.k_line_density', 12.3, 'k-line density (Ã…)', 'k_line_density=12.3'], + ['datetime', 'upload_create_time', 0, 'Upload create time', 'upload_create_time=01/01/1970'], ['boolean', 'results.properties.electronic.dos_electronic.spin_polarized', 'false', 'Spin-polarized', 'false'] ])('%s', async (name, quantity, input, title, output) => { const context = ui.apps.options.entries diff --git a/gui/src/components/search/FilterTitle.js b/gui/src/components/search/FilterTitle.js index e1d3e2c80e..0ca7ee1a90 100644 --- a/gui/src/components/search/FilterTitle.js +++ b/gui/src/components/search/FilterTitle.js @@ -58,7 +58,6 @@ const FilterTitle = React.memo(({ description, unit, variant, - full, TooltipProps, onMouseDown, onMouseUp, @@ -75,9 +74,7 @@ const FilterTitle = React.memo(({ // Create the final label const finalLabel = useMemo(() => { - let finalLabel = label || (full - ? filterData[quantity]?.labelFull - : filterData[quantity]?.label) + let finalLabel = label || filterData[quantity]?.label if (!disableUnit) { let finalUnit if (unit) { @@ -90,7 +87,7 @@ const FilterTitle = React.memo(({ } } return finalLabel - }, [filterData, quantity, units, unit, label, disableUnit, full]) + }, [filterData, quantity, units, label, unit, disableUnit]) // Determine the final description const finalDescription = description || filterData[quantity]?.description || '' @@ -120,7 +117,6 @@ FilterTitle.propTypes = { unit: PropTypes.string, description: PropTypes.string, variant: PropTypes.string, - full: PropTypes.bool, className: PropTypes.string, classes: PropTypes.object, rotation: PropTypes.oneOf(['up', 'right', 'down']), diff --git a/gui/src/components/search/FilterTitle.spec.js b/gui/src/components/search/FilterTitle.spec.js index 8a47fd6b3c..cfce842934 100644 --- a/gui/src/components/search/FilterTitle.spec.js +++ b/gui/src/components/search/FilterTitle.spec.js @@ -27,7 +27,7 @@ test.each([ }, { quantity: 'results.method.simulation.dft.hubbard_kanamori_model.u_effective', - label: 'U Effective', + label: 'U effective', description: 'Value of the effective U parameter (u - j).', unit: 'eV' } @@ -52,7 +52,7 @@ test.each([ }, { quantity: 'results.method.simulation.dft.hubbard_kanamori_model.u_effective', - label: 'U Effective', + label: 'U effective', description: 'This is a custom description.', unit: 'eV' } @@ -77,7 +77,7 @@ test.each([ }, { quantity: 'results.method.simulation.dft.hubbard_kanamori_model.u_effective', - label: 'U Effective', + label: 'U effective', unit: undefined } ] diff --git a/gui/src/components/search/SearchContext.js b/gui/src/components/search/SearchContext.js index 8b3ee59e21..f00f48615e 100644 --- a/gui/src/components/search/SearchContext.js +++ b/gui/src/components/search/SearchContext.js @@ -250,6 +250,7 @@ export const SearchContextRaw = React.memo(({ let options = config?.options ? Object.values(config.options) : [] options.forEach(option => { const unit = option.unit + option.label = option.label || initialFilterData[option.quantity || option.key]?.label if (unit) { option.unit = new Unit(unit) option.label = `${option.label} (${option.unit.label()})` @@ -302,10 +303,12 @@ export const SearchContextRaw = React.memo(({ : <i>no entry time</i> }, authors: { + sortable: false, render: row => authorList(row), align: 'left' }, references: { + sortable: false, render: row => { const refs = row.references || [] if (refs.length > 0) { @@ -322,6 +325,7 @@ export const SearchContextRaw = React.memo(({ } }, datasets: { + sortable: false, render: entry => { const datasets = entry.datasets || [] if (datasets.length > 0) { diff --git a/gui/src/components/search/SearchContext.spec.js b/gui/src/components/search/SearchContext.spec.js index 46740c663e..34a776cfdc 100644 --- a/gui/src/components/search/SearchContext.spec.js +++ b/gui/src/components/search/SearchContext.spec.js @@ -105,7 +105,6 @@ describe('reading query from URL', function() { const { result: resultUseSearchContext } = renderHook(() => useSearchContext(), { wrapper: WrapperSearch }) const { result: resultUseQuery } = renderHook(() => resultUseSearchContext.current.useQuery(), { wrapper: WrapperDefault}) const query = resultUseQuery.current - console.log(query) expect(query).toMatchObject(expected_query) }) }) diff --git a/gui/src/components/search/conftest.spec.js b/gui/src/components/search/conftest.spec.js index af45df7b7e..a1495cc60d 100644 --- a/gui/src/components/search/conftest.spec.js +++ b/gui/src/components/search/conftest.spec.js @@ -27,7 +27,7 @@ import userEvent from '@testing-library/user-event' import { SearchContext } from './SearchContext' import { defaultFilterData } from './FilterRegistry' import { format } from 'date-fns' -import { DType, parseJMESPath } from '../../utils' +import { DType, getDisplayLabel, parseJMESPath } from '../../utils' import { Unit } from '../units/Unit' import { ui } from '../../config' import { menuMap } from './menus/FilterMainMenu' @@ -337,7 +337,7 @@ export async function expectSearchResults(context, root = screen) { const columnLabels = columnConfig.selected.map(key => { const config = columnConfig.options[key] const unit = config.unit - const label = config.label + const label = config.label || defaultFilterData[key]?.label || getDisplayLabel({name: key.split('.').slice(-1)[0]}) return unit ? `${label} (${new Unit(unit).label()})` : label diff --git a/gui/src/components/search/widgets/Dashboard.spec.js b/gui/src/components/search/widgets/Dashboard.spec.js index 1ce25ff338..a273e72514 100644 --- a/gui/src/components/search/widgets/Dashboard.spec.js +++ b/gui/src/components/search/widgets/Dashboard.spec.js @@ -87,7 +87,7 @@ describe('displaying an initial widget and removing it', () => { 'scatterplot', { type: 'scatterplot', - label: 'Test label', + title: 'Test title', description: 'Custom scatter plot', x: {quantity: 'results.properties.optoelectronic.solar_cell.open_circuit_voltage'}, y: {quantity: 'results.properties.optoelectronic.solar_cell.efficiency'}, @@ -148,7 +148,7 @@ describe('displaying an initial widget and removing it', () => { // Remove widget, check that it is gone. A test id is used to fetch the // remove button since it is an icon that may appear in several locations. const removeButton = screen.getByTestId(`0-remove-widget`) - const label = widget.label || defaultFilterData[widget.quantity].label + const label = widget.title || defaultFilterData[widget.quantity].label expect(screen.queryByText(label, {exact: false})).toBeInTheDocument() await userEvent.click(removeButton) expect(screen.queryByText(label, {exact: false})).not.toBeInTheDocument() diff --git a/gui/src/components/search/widgets/Widget.js b/gui/src/components/search/widgets/Widget.js index 38d9aaf83a..37ccc97705 100644 --- a/gui/src/components/search/widgets/Widget.js +++ b/gui/src/components/search/widgets/Widget.js @@ -37,7 +37,7 @@ const useStyles = makeStyles(theme => ({ export const Widget = React.memo(({ id, quantity, - label, + title, description, onEdit, className, @@ -63,7 +63,7 @@ export const Widget = React.memo(({ <WidgetHeader id={id} quantity={quantity} - label={label} + label={title} description={description} actions={actionsFinal} anchored @@ -75,7 +75,7 @@ export const Widget = React.memo(({ Widget.propTypes = { id: PropTypes.string.isRequired, quantity: PropTypes.string, - label: PropTypes.string, + title: PropTypes.string, description: PropTypes.string, onEdit: PropTypes.func, className: PropTypes.string, @@ -98,6 +98,7 @@ export const schemaLayout = object({ }) export const schemaWidget = object({ type: string().required(), + title: string(), layout: object({ sm: schemaLayout, md: schemaLayout, diff --git a/gui/src/components/search/widgets/WidgetHeader.spec.js b/gui/src/components/search/widgets/WidgetHeader.spec.js index b3ad67acf0..fa9f79ced2 100644 --- a/gui/src/components/search/widgets/WidgetHeader.spec.js +++ b/gui/src/components/search/widgets/WidgetHeader.spec.js @@ -38,7 +38,7 @@ test.each([ {quantity: 'results.properties.electronic.band_structure_electronic.band_gap.value'}, { quantity: 'results.properties.electronic.band_structure_electronic.band_gap.value', - label: 'Band Gap Value', + label: 'Value', unit: 'eV' } ] diff --git a/gui/src/components/search/widgets/WidgetHistogram.js b/gui/src/components/search/widgets/WidgetHistogram.js index 34aeab59b0..9e76e88397 100644 --- a/gui/src/components/search/widgets/WidgetHistogram.js +++ b/gui/src/components/search/widgets/WidgetHistogram.js @@ -26,6 +26,7 @@ import { } from '@material-ui/core' import { useSearchContext } from '../SearchContext' import { InputMetainfo } from '../input/InputMetainfo' +import { InputTextField } from '../input/InputText' import { Widget, schemaWidget } from './Widget' import { ActionCheckbox, ActionSelect } from '../../Actions' import { WidgetEditDialog, WidgetEditGroup, WidgetEditOption } from './WidgetEdit' @@ -43,7 +44,7 @@ export const autorangeDescription = 'Automatically center the view on the data' export const WidgetHistogram = React.memo(( { id, - label, + title, description, quantity, nbins, @@ -66,7 +67,7 @@ export const WidgetHistogram = React.memo(( return <Widget id={id} quantity={quantity} - label={label} + title={title} description={description} onEdit={handleEdit} className={className} @@ -101,7 +102,7 @@ export const WidgetHistogram = React.memo(( WidgetHistogram.propTypes = { id: PropTypes.string.isRequired, - label: PropTypes.string, + title: PropTypes.string, description: PropTypes.string, quantity: PropTypes.string, nbins: PropTypes.number, @@ -209,6 +210,14 @@ export const WidgetHistogramEdit = React.memo((props) => { </WidgetEditOption> </WidgetEditGroup> <WidgetEditGroup title="general"> + <WidgetEditOption> + <InputTextField + label="title" + fullWidth + value={settings?.title} + onChange={(event) => handleChange('title', event.target.value)} + /> + </WidgetEditOption> <WidgetEditOption> <FormControlLabel control={<Checkbox checked={settings.autorange} onChange={(event, value) => handleChange('autorange', value)}/>} diff --git a/gui/src/components/search/widgets/WidgetPeriodicTable.js b/gui/src/components/search/widgets/WidgetPeriodicTable.js index 71fd0dd419..a27aedfea4 100644 --- a/gui/src/components/search/widgets/WidgetPeriodicTable.js +++ b/gui/src/components/search/widgets/WidgetPeriodicTable.js @@ -23,6 +23,7 @@ import { useSearchContext } from '../SearchContext' import { Widget, schemaWidget } from './Widget' import { ActionSelect } from '../../Actions' import { WidgetEditDialog, WidgetEditGroup, WidgetEditOption } from './WidgetEdit' +import { InputTextField } from '../input/InputText' import { PeriodicTable } from '../input/InputPeriodicTable' import { scales } from '../../plotting/common' @@ -32,7 +33,7 @@ import { scales } from '../../plotting/common' export const WidgetPeriodicTable = React.memo(( { id, - label, + title, description, quantity, scale, @@ -52,7 +53,7 @@ export const WidgetPeriodicTable = React.memo(( return <Widget id={id} quantity={quantity} - label={label} + title={title} description={description} onEdit={handleEdit} className={className} @@ -81,7 +82,7 @@ export const WidgetPeriodicTable = React.memo(( WidgetPeriodicTable.propTypes = { id: PropTypes.string.isRequired, - label: PropTypes.string, + title: PropTypes.string, description: PropTypes.string, quantity: PropTypes.string, scale: PropTypes.string, @@ -160,6 +161,16 @@ export const WidgetPeriodicTableEdit = React.memo((props) => { </TextField> </WidgetEditOption> </WidgetEditGroup> + <WidgetEditGroup title="General"> + <WidgetEditOption> + <InputTextField + label="title" + fullWidth + value={settings?.title} + onChange={(event) => handleChange('title', event.target.value)} + /> + </WidgetEditOption> + </WidgetEditGroup> </WidgetEditDialog> }) diff --git a/gui/src/components/search/widgets/WidgetScatterPlot.js b/gui/src/components/search/widgets/WidgetScatterPlot.js index 9cd059ff00..727245e9e8 100644 --- a/gui/src/components/search/widgets/WidgetScatterPlot.js +++ b/gui/src/components/search/widgets/WidgetScatterPlot.js @@ -17,20 +17,16 @@ */ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react' import PropTypes from 'prop-types' -import { number, bool, reach } from 'yup' +import { number, bool } from 'yup' import jmespath from 'jmespath' -import { isEmpty, isArray, isEqual, cloneDeep, range, isNil, flattenDeep } from 'lodash' +import { isEmpty, isArray, isEqual, range, isNil, flattenDeep } from 'lodash' import { Divider, Tooltip, - makeStyles, - Checkbox, - FormControlLabel + makeStyles } from '@material-ui/core' import { ToggleButton, ToggleButtonGroup, Alert } from '@material-ui/lab' -import { InputJMESPath } from '../input/InputMetainfo' import { Widget, schemaWidget, schemaAxis, schemaMarkers } from './Widget' -import { WidgetEditDialog, WidgetEditGroup, WidgetEditOption, WidgetEditSelect } from './WidgetEdit' import { useSearchContext } from '../SearchContext' import Floatable from '../../visualization/Floatable' import PlotScatter from '../../plotting/PlotScatter' @@ -38,21 +34,10 @@ import { Action, ActionCheckbox } from '../../Actions' import { CropFree, PanTool, Fullscreen, Replay } from '@material-ui/icons' import { autorangeDescription } from './WidgetHistogram' import { styled } from '@material-ui/core/styles' -import { DType, setDeep, parseJMESPath } from '../../../utils' +import {DType, parseJMESPath, getDisplayLabel} from '../../../utils' import { Quantity } from '../../units/Quantity' import { Unit } from '../../units/Unit' import { useUnitContext } from '../../units/UnitContext' -import { InputTextField } from '../input/InputText' -import UnitInput from '../../units/UnitInput' - -// Predefined in order to not break memoization -const dtypesNumeric = new Set([DType.Int, DType.Float]) -const dtypesColor = new Set([DType.String, DType.Enum, DType.Float, DType.Int]) -const nPointsOptions = { - 100: 100, - 1000: 1000, - 10000: 10000 -} const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({ '& .MuiToggleButtonGroup-grouped': { @@ -89,7 +74,7 @@ const useStyles = makeStyles((theme) => ({ export const WidgetScatterPlot = React.memo(( { id, - label, + title, description, x, y, @@ -137,11 +122,11 @@ export const WidgetScatterPlot = React.memo(( const xFilter = filterData[xParsed.quantity] const yFilter = filterData[yParsed.quantity] const colorFilter = filterData[colorParsed.quantity] - const xTitle = x.title || xFilter?.label - const yTitle = y.title || yFilter?.label + const xTitle = x.title || xFilter?.label || getDisplayLabel(xFilter) + const yTitle = y.title || yFilter?.label || getDisplayLabel(yFilter) const xType = xFilter?.dtype const yType = yFilter?.dtype - const colorTitle = markers?.color?.title || colorFilter?.label + const colorTitle = markers?.color?.title || colorFilter?.label || getDisplayLabel(colorFilter) const unitLabelX = displayUnitX.label() const unitLabelY = displayUnitY.label() const unitLabelColor = displayUnitColor.label() @@ -391,7 +376,7 @@ export const WidgetScatterPlot = React.memo(( > <Widget id={id} - label={label || "Scatter plot"} + title={title || "Scatter plot"} description={description || 'Custom scatter plot'} onEdit={handleEdit} actions={actions} @@ -422,7 +407,7 @@ export const WidgetScatterPlot = React.memo(( WidgetScatterPlot.propTypes = { id: PropTypes.string.isRequired, - label: PropTypes.string, + title: PropTypes.string, description: PropTypes.string, x: PropTypes.object, y: PropTypes.object, @@ -434,186 +419,6 @@ WidgetScatterPlot.propTypes = { onSelected: PropTypes.func } -/** - * A dialog that is used to configure a scatter plot widget. - */ -export const WidgetScatterPlotEdit = React.memo(({widget}) => { - const { filterData, useSetWidget } = useSearchContext() - const [settings, setSettings] = useState(cloneDeep(widget)) - const [errors, setErrors] = useState({}) - const [dimensions, setDimensions] = useState({}) - const setWidget = useSetWidget(widget.id) - - const handleError = useCallback((key, value) => { - setErrors(old => ({...old, [key]: value})) - }, [setErrors]) - - const handleErrorQuantity = useCallback((key, value) => { - handleError(key, value) - setDimensions((old) => ({...old, [key]: null})) - }, [handleError]) - - const handleChange = useCallback((key, value) => { - setSettings(old => { - const newValue = {...old} - setDeep(newValue, key, value) - return newValue - }) - }, [setSettings]) - - const handleClose = useCallback(() => { - setWidget(old => ({...old, editing: false})) - }, [setWidget]) - - const handleAccept = useCallback((key, value) => { - try { - reach(schemaWidgetScatterPlot, key).validateSync(value) - } catch (e) { - handleError(key, e.message) - return - } - setErrors(old => ({...old, [key]: undefined})) - handleChange(key, value) - }, [handleError, handleChange]) - - const handleAcceptQuantity = useCallback((key, value) => { - handleAccept(key, value) - const { quantity } = parseJMESPath(value) - const dimension = filterData[quantity]?.dimension - setDimensions((old) => ({...old, [key]: dimension})) - }, [handleAccept, filterData]) - - // Upon accepting the entire form, we perform final validation that also - // takes into account cross-field incompatibilities - const handleEditAccept = useCallback(() => { - // Check for independent errors from components - const independentErrors = Object.values(errors).some(x => !!x) - if (!independentErrors) { - setWidget(old => ({...old, ...{...settings, editing: false, visible: true}})) - } - }, [settings, setWidget, errors]) - - return <WidgetEditDialog - id={widget.id} - open={widget.editing} - visible={widget.visible} - title="Edit scatter plot widget" - onClose={handleClose} - onAccept={handleEditAccept} - > - <WidgetEditGroup title="x axis"> - <WidgetEditOption> - <InputJMESPath - label="quantity" - value={settings.x?.quantity} - onChange={(value) => handleChange('x.quantity', value)} - onSelect={(value) => handleAcceptQuantity('x.quantity', value)} - onAccept={(value) => handleAcceptQuantity('x.quantity', value)} - error={errors['x.quantity']} - onError={(value) => handleErrorQuantity('x.quantity', value)} - dtypes={dtypesNumeric} - dtypesRepeatable={dtypesNumeric} - /> - </WidgetEditOption> - <WidgetEditOption> - <InputTextField - label="title" - fullWidth - value={settings.x?.title} - onChange={(event) => handleChange('x.title', event.target.value)} - /> - </WidgetEditOption> - <WidgetEditOption> - <UnitInput - label='unit' - value={settings.x?.unit} - onChange={(value) => handleChange('x.unit', value)} - onSelect={(value) => handleAccept('x.unit', value)} - onAccept={(value) => handleAccept('x.unit', value)} - error={errors['x.unit']} - onError={(value) => handleError('x.unit', value)} - dimension={dimensions['x.quantity'] || null} - optional - disableGroup - /> - </WidgetEditOption> - </WidgetEditGroup> - <WidgetEditGroup title="y axis"> - <WidgetEditOption> - <InputJMESPath - label="quantity" - value={settings.y?.quantity} - onChange={(value) => handleChange('y.quantity', value)} - onSelect={(value) => handleAcceptQuantity('y.quantity', value)} - onAccept={(value) => handleAcceptQuantity('y.quantity', value)} - error={errors['y.quantity']} - onError={(value) => handleErrorQuantity('y.quantity', value)} - dtypes={dtypesNumeric} - dtypesRepeatable={dtypesNumeric} - /> - </WidgetEditOption> - <WidgetEditOption> - <InputTextField - label="title" - fullWidth - value={settings.y?.title} - onChange={(event) => handleChange('y.title', event.target.value)} - /> - </WidgetEditOption> - <WidgetEditOption> - <UnitInput - label='unit' - value={settings.y?.unit} - onChange={(value) => handleChange('y.unit', value)} - onSelect={(value) => handleAccept('y.unit', value)} - onAccept={(value) => handleAccept('y.unit', value)} - error={errors['y.unit']} - onError={(value) => handleError('y.unit', value)} - dimension={dimensions['y.quantity'] || null} - optional - disableGroup - /> - </WidgetEditOption> - </WidgetEditGroup> - <WidgetEditGroup title="marker color"> - <WidgetEditOption> - <InputJMESPath - label="quantity" - value={settings?.markers?.color?.quantity} - onChange={(value) => handleChange('markers.color.quantity', value)} - onSelect={(value) => handleAccept('markers.color.quantity', value)} - onAccept={(value) => handleAccept('markers.color.quantity', value)} - error={errors['markers.color.quantity']} - onError={(value) => handleError('markers.color.quantity', value)} - dtypes={dtypesColor} - dtypesRepeatable={dtypesColor} - optional - /> - </WidgetEditOption> - </WidgetEditGroup> - <WidgetEditGroup title="general"> - <WidgetEditOption> - <WidgetEditSelect - label="Maximum number of entries to load" - options={nPointsOptions} - value={settings.size} - onChange={(event) => { handleChange('size', event.target.value) }} - /> - </WidgetEditOption> - <WidgetEditOption> - <FormControlLabel - control={<Checkbox checked={settings.autorange} onChange={(event, value) => handleChange('autorange', value)}/>} - label={autorangeDescription} - /> - </WidgetEditOption> - </WidgetEditGroup> - </WidgetEditDialog> -}) - -WidgetScatterPlotEdit.propTypes = { - widget: PropTypes.object -} - export const schemaWidgetScatterPlot = schemaWidget.shape({ x: schemaAxis.required('Quantity for the x axis is required.'), y: schemaAxis.required('Quantity for the y axis is required.'), diff --git a/gui/src/components/search/widgets/WidgetScatterPlot.spec.js b/gui/src/components/search/widgets/WidgetScatterPlot.spec.js index 657dd2140f..df96c0adcb 100644 --- a/gui/src/components/search/widgets/WidgetScatterPlot.spec.js +++ b/gui/src/components/search/widgets/WidgetScatterPlot.spec.js @@ -129,9 +129,9 @@ describe('test custom axis titles', () => { describe('test custom axis units', () => { test.each([ - ['x', {x: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}, 'Final Energy Difference (Ha)'], - ['y', {y: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}, 'Final Energy Difference (Ha)'], - ['color', {markers: {color: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}}, 'Final Energy Difference (Ha)'] + ['x', {x: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}, 'Final energy difference (Ha)'], + ['y', {y: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}, 'Final energy difference (Ha)'], + ['color', {markers: {color: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}}, 'Final energy difference (Ha)'] ])('%s', async (name, config, title) => { const configFinal = { id: '0', diff --git a/gui/src/components/search/widgets/WidgetScatterPlotEdit.js b/gui/src/components/search/widgets/WidgetScatterPlotEdit.js index 165e7cd6b6..8c43207d26 100644 --- a/gui/src/components/search/widgets/WidgetScatterPlotEdit.js +++ b/gui/src/components/search/widgets/WidgetScatterPlotEdit.js @@ -237,6 +237,14 @@ export const WidgetScatterPlotEdit = React.memo(({widget}) => { </WidgetEditOption> </WidgetEditGroup> <WidgetEditGroup title="general"> + <WidgetEditOption> + <InputTextField + label="title" + fullWidth + value={settings?.title} + onChange={(event) => handleChange('title', event.target.value)} + /> + </WidgetEditOption> <WidgetEditOption> <WidgetEditSelect label="Maximum number of entries to load" diff --git a/gui/src/components/search/widgets/WidgetTerms.js b/gui/src/components/search/widgets/WidgetTerms.js index 4eb9dd0135..a4289f5ec9 100644 --- a/gui/src/components/search/widgets/WidgetTerms.js +++ b/gui/src/components/search/widgets/WidgetTerms.js @@ -31,7 +31,7 @@ import { import { useResizeDetector } from 'react-resize-detector' import { useSearchContext } from '../SearchContext' import { InputMetainfo } from '../input/InputMetainfo' -import { InputTextQuantity } from '../input/InputText' +import { InputTextQuantity, InputTextField } from '../input/InputText' import InputItem, { inputItemHeight } from '../input/InputItem' import InputUnavailable from '../input/InputUnavailable' import InputTooltip from '../input/InputTooltip' @@ -100,7 +100,7 @@ const useStyles = makeStyles(theme => ({ export const WidgetTerms = React.memo(( { id, - label, + title, description, quantity, scale, @@ -189,7 +189,7 @@ export const WidgetTerms = React.memo(( return <Widget id={id} quantity={quantity} - label={label} + title={title} description={description} onEdit={handleEdit} className={clsx(className)} @@ -236,7 +236,7 @@ export const WidgetTerms = React.memo(( WidgetTerms.propTypes = { id: PropTypes.string.isRequired, - label: PropTypes.string, + title: PropTypes.string, description: PropTypes.string, quantity: PropTypes.string, nbins: PropTypes.number, @@ -334,6 +334,14 @@ export const WidgetTermsEdit = React.memo((props) => { </WidgetEditOption> </WidgetEditGroup> <WidgetEditGroup title="general"> + <WidgetEditOption> + <InputTextField + label="title" + fullWidth + value={settings?.title} + onChange={(event) => handleChange('title', event.target.value)} + /> + </WidgetEditOption> <WidgetEditOption> <FormControlLabel control={<Checkbox checked={settings.showinput} onChange={(event, value) => handleChange('showinput', value)}/>} diff --git a/gui/src/components/uploads/ProcessingTable.js b/gui/src/components/uploads/ProcessingTable.js index 93fdbc8522..0112e5cd97 100644 --- a/gui/src/components/uploads/ProcessingTable.js +++ b/gui/src/components/uploads/ProcessingTable.js @@ -119,7 +119,7 @@ export default function ProcessingTable(props) { return {entry_id: [...selected]} }, [selected, upload]) - return <Paper> + return <Paper data-testid={'processing-table'}> <Datatable columns={columns} shownColumns={defaultSelectedColumns} {...props} selected={selected} getId={option => option.entry_id} onSelectedChanged={setSelected} diff --git a/gui/src/components/uploads/UploadPage.spec.js b/gui/src/components/uploads/UploadPage.spec.js index d615524f8e..7fff151cdf 100644 --- a/gui/src/components/uploads/UploadPage.spec.js +++ b/gui/src/components/uploads/UploadPage.spec.js @@ -31,11 +31,12 @@ import userEvent from '@testing-library/user-event' afterEach(() => closeAPI()) const testShownColumnsAction = async () => { - await screen.findByTitle('Change the shown columns.') - expect(screen.queryByText('Parser name')).not.toBeInTheDocument() - expect(screen.queryByText('Comment')).not.toBeInTheDocument() - expect(screen.queryByText('References')).not.toBeInTheDocument() - expect(screen.queryByText('Datasets')).not.toBeInTheDocument() + await screen.findByTitle('Change the shown columns') + const processingTable = screen.getByTestId('processing-table') + expect(within(processingTable).queryByText('Parser name')).not.toBeInTheDocument() + expect(within(processingTable).queryByText('Comment')).not.toBeInTheDocument() + expect(within(processingTable).queryByText('References')).not.toBeInTheDocument() + expect(within(processingTable).queryByText('Datasets')).not.toBeInTheDocument() const uploadPageRows = screen.queryAllByTestId('datatable-row') expect(uploadPageRows.length).toBe(1) @@ -43,15 +44,16 @@ const testShownColumnsAction = async () => { expect(within(uploadPageRows[0]).queryByText('doi')).not.toBeInTheDocument() expect(within(uploadPageRows[0]).queryByText('no datasets')).not.toBeInTheDocument() - act(() => { fireEvent.click(screen.getByTitle('Change the shown columns.')) }) - await waitFor(() => expect(screen.queryByText('Datasets')).toBeInTheDocument()) - await waitFor(() => expect(screen.queryByText('Comment')).toBeInTheDocument()) - await waitFor(() => expect(screen.queryByText('References')).toBeInTheDocument()) - await waitFor(() => expect(screen.queryByText('Parser name')).toBeInTheDocument()) + act(() => { fireEvent.click(screen.getByTitle('Change the shown columns')) }) + const selectMenu = screen.getByTestId('column-select-menu') + await waitFor(() => expect(within(selectMenu).queryByText('Datasets')).toBeInTheDocument()) + await waitFor(() => expect(within(selectMenu).queryByText('Comment')).toBeInTheDocument()) + await waitFor(() => expect(within(selectMenu).queryByText('References')).toBeInTheDocument()) + await waitFor(() => expect(within(selectMenu).queryByText('Parser name')).toBeInTheDocument()) - act(() => { fireEvent.click(screen.getByText('Comment')) }) - act(() => { fireEvent.click(screen.getByText('References')) }) - act(() => { fireEvent.click(screen.getByText('Datasets')) }) + act(() => { fireEvent.click(within(selectMenu).getByText('Comment')) }) + act(() => { fireEvent.click(within(selectMenu).getByText('References')) }) + act(() => { fireEvent.click(within(selectMenu).getByText('Datasets')) }) await waitFor(() => expect(within(uploadPageRows[0]).queryByText('Mocked')).toBeInTheDocument()) await waitFor(() => expect(within(uploadPageRows[0]).queryByText('doi')).toBeInTheDocument()) await waitFor(() => expect(within(uploadPageRows[0]).queryByText('no datasets')).toBeInTheDocument()) diff --git a/gui/src/utils.js b/gui/src/utils.js index 9b3b46cb7b..decac31725 100644 --- a/gui/src/utils.js +++ b/gui/src/utils.js @@ -1738,3 +1738,31 @@ export function cleanse(obj) { } } } + +/** + * Get the display label of the quantity + * @param {*} def The quantity definition + * @param {*} isArchive Determines the archive scope + * @param {*} technicalView Returns the technical exploring name + */ +export function getDisplayLabel(def, isArchive = false, technicalView = false) { + if (!def) { + return undefined + } + if (technicalView) { + return def.name + } + const labelAnnotation = def?.m_annotations?.display?.[0]?.label + const eln = def?.m_annotations?.eln + if (labelAnnotation) { + if (typeof labelAnnotation === 'string') { + return labelAnnotation + } else { + throw new Error('Unsupported format for label annotation') + } + } + const name = def.m_def === 'nomad.metainfo.metainfo.Section' || def.m_def === 'nomad.metainfo.metainfo.SubSection' + ? def.name.replace(/([a-z])([A-Z])/g, '$1 $2') + : def.name.replace(/_/g, ' ') + return eln?.[0].label || def?.more?.label || def?.label || (isArchive ? name : capitalize(name)) +} diff --git a/gui/tests/artifacts.js b/gui/tests/artifacts.js index b765918f1c..bd59789aa0 100644 --- a/gui/tests/artifacts.js +++ b/gui/tests/artifacts.js @@ -40363,7 +40363,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Short name" + "label": "short name" } ] }, @@ -41241,7 +41241,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Substance name" + "label": "substance name" } ] }, @@ -41260,7 +41260,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Substance ID" + "label": "substance ID" } ] }, @@ -41482,7 +41482,7 @@ window.nomadArtifacts = { "eln": [ { "component": "RichTextEditQuantity", - "label": "Detailed substance description" + "label": "detailed substance description" } ] }, @@ -57359,7 +57359,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Step name" + "label": "step name" } ] }, @@ -57378,7 +57378,7 @@ window.nomadArtifacts = { "eln": [ { "component": "DateTimeEditQuantity", - "label": "Starting time" + "label": "starting time" } ] }, @@ -57430,7 +57430,7 @@ window.nomadArtifacts = { "eln": [ { "component": "DateTimeEditQuantity", - "label": "Starting Time" + "label": "starting Time" } ] }, @@ -57519,7 +57519,7 @@ window.nomadArtifacts = { "eln": [ { "component": "ReferenceEditQuantity", - "label": "Section Reference" + "label": "section reference" } ] }, @@ -57550,7 +57550,7 @@ window.nomadArtifacts = { "eln": [ { "component": "ReferenceEditQuantity", - "label": "Entity Reference" + "label": "entity reference" } ] }, @@ -57617,7 +57617,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Activity ID" + "label": "activity ID" } ] }, @@ -57909,7 +57909,7 @@ window.nomadArtifacts = { "eln": [ { "component": "ReferenceEditQuantity", - "label": "Instrument Reference" + "label": "instrument reference" } ] }, @@ -57940,7 +57940,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Component label" + "label": "component label" } ] }, @@ -58039,7 +58039,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Substance name" + "label": "substance name" } ] }, @@ -58277,7 +58277,7 @@ window.nomadArtifacts = { "eln": [ { "component": "ReferenceEditQuantity", - "label": "Composite System Reference" + "label": "composite system reference" } ] }, @@ -58343,7 +58343,7 @@ window.nomadArtifacts = { "eln": [ { "component": "DateTimeEditQuantity", - "label": "Ending Time" + "label": "ending time" } ] }, @@ -58545,7 +58545,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Substance name" + "label": "substance name" } ] }, @@ -58564,7 +58564,7 @@ window.nomadArtifacts = { "eln": [ { "component": "StringEditQuantity", - "label": "Substance ID" + "label": "substance ID" } ] }, @@ -58583,7 +58583,7 @@ window.nomadArtifacts = { "eln": [ { "component": "RichTextEditQuantity", - "label": "Detailed substance description" + "label": "detailed substance description" } ] }, diff --git a/gui/tests/env.js b/gui/tests/env.js index 565e0c2ead..51abd35805 100644 --- a/gui/tests/env.js +++ b/gui/tests/env.js @@ -453,19 +453,15 @@ window.nomadEnv = { "align": "left" }, "entry_type": { - "label": "Entry type", "align": "left" }, "entry_create_time": { - "label": "Entry creation time", "align": "left" }, "upload_name": { - "label": "Upload name", "align": "left" }, "upload_id": { - "label": "Upload id", "align": "left" }, "upload_create_time": { @@ -473,91 +469,69 @@ window.nomadEnv = { "align": "left" }, "authors": { - "label": "Authors", "align": "left" }, "results.method.method_name": { - "label": "Method name", "align": "left" }, "results.method.simulation.program_name": { - "label": "Program name", "align": "left" }, "results.method.simulation.dft.xc_functional_type": { - "label": "XC Functional Type", "align": "left" }, "results.method.simulation.precision.apw_cutoff": { - "label": "APW Cutoff", "align": "left" }, "results.method.simulation.precision.basis_set": { - "label": "Basis Set", "align": "left" }, "results.method.simulation.precision.k_line_density": { - "label": "k-line Density", "align": "left" }, "results.method.simulation.precision.native_tier": { - "label": "Code-specific tier", "align": "left" }, "results.method.simulation.precision.planewave_cutoff": { - "label": "Plane-wave cutoff", "align": "left" }, "results.material.structural_type": { - "label": "Dimensionality", "align": "left" }, "results.material.symmetry.crystal_system": { - "label": "Crystal system", "align": "left" }, "results.material.symmetry.space_group_symbol": { - "label": "Space group symbol", "align": "left" }, "results.material.symmetry.space_group_number": { - "label": "Space group number", "align": "left" }, "results.eln.lab_ids": { - "label": "Lab IDs", "align": "left" }, "results.eln.sections": { - "label": "Sections", "align": "left" }, "results.eln.methods": { - "label": "Methods", "align": "left" }, "results.eln.tags": { - "label": "Tags", "align": "left" }, "results.eln.instruments": { - "label": "Instruments", "align": "left" }, "mainfile": { - "label": "Mainfile", "align": "left" }, "comment": { - "label": "Comment", "align": "left" }, "references": { - "label": "References", "align": "left" }, "datasets": { - "label": "Datasets", "align": "left" }, "published": { @@ -740,15 +714,12 @@ window.nomadEnv = { "align": "left" }, "results.method.simulation.program_name": { - "label": "Program name", "align": "left" }, "results.method.method_name": { - "label": "Method name", "align": "left" }, "results.method.simulation.dft.xc_functional_type": { - "label": "Jacob's ladder", "align": "left" }, "upload_create_time": { @@ -756,43 +727,33 @@ window.nomadEnv = { "align": "left" }, "authors": { - "label": "Authors", "align": "left" }, "results.method.simulation.precision.apw_cutoff": { - "label": "APW Cutoff", "align": "left" }, "results.method.simulation.precision.basis_set": { - "label": "Basis Set", "align": "left" }, "results.method.simulation.precision.k_line_density": { - "label": "k-line Density", "align": "left" }, "results.method.simulation.precision.native_tier": { - "label": "Code-specific tier", "align": "left" }, "results.method.simulation.precision.planewave_cutoff": { - "label": "Plane-wave cutoff", "align": "left" }, "results.material.structural_type": { - "label": "Dimensionality", "align": "left" }, "results.material.symmetry.crystal_system": { - "label": "Crystal system", "align": "left" }, "results.material.symmetry.space_group_symbol": { - "label": "Space group symbol", "align": "left" }, "results.material.symmetry.space_group_number": { - "label": "Space group number", "align": "left" }, "entry_name": { @@ -800,19 +761,15 @@ window.nomadEnv = { "align": "left" }, "mainfile": { - "label": "Mainfile", "align": "left" }, "comment": { - "label": "Comment", "align": "left" }, "references": { - "label": "References", "align": "left" }, "datasets": { - "label": "Datasets", "align": "left" }, "published": { @@ -1222,27 +1179,21 @@ window.nomadEnv = { "align": "left" }, "structural_type": { - "label": "Dimensionality", "align": "left" }, "symmetry.structure_name": { - "label": "Structure name", "align": "left" }, "symmetry.space_group_number": { - "label": "Space group number", "align": "left" }, "symmetry.crystal_system": { - "label": "Crystal system", "align": "left" }, "symmetry.space_group_symbol": { - "label": "Space group symbol", "align": "left" }, "material_id": { - "label": "Material ID", "align": "left" } }, @@ -1408,7 +1359,6 @@ window.nomadEnv = { "align": "left" }, "entry_type": { - "label": "Entry type", "align": "left" }, "upload_create_time": { @@ -1416,7 +1366,6 @@ window.nomadEnv = { "align": "left" }, "authors": { - "label": "Authors", "align": "left" }, "results.material.chemical_formula_hill": { @@ -1424,43 +1373,33 @@ window.nomadEnv = { "align": "left" }, "results.method.method_name": { - "label": "Method name", "align": "left" }, "results.eln.lab_ids": { - "label": "Lab IDs", "align": "left" }, "results.eln.sections": { - "label": "Sections", "align": "left" }, "results.eln.methods": { - "label": "Methods", "align": "left" }, "results.eln.tags": { - "label": "Tags", "align": "left" }, "results.eln.instruments": { - "label": "Instruments", "align": "left" }, "mainfile": { - "label": "Mainfile", "align": "left" }, "comment": { - "label": "Comment", "align": "left" }, "references": { - "label": "References", "align": "left" }, "datasets": { - "label": "Datasets", "align": "left" }, "published": { @@ -1555,11 +1494,9 @@ window.nomadEnv = { "align": "left" }, "results.properties.spectroscopic.spectra.provenance.eels.detector_type": { - "label": "Detector type", "align": "left" }, "results.properties.spectroscopic.spectra.provenance.eels.resolution": { - "label": "Resolution", "align": "left" }, "upload_create_time": { @@ -1567,15 +1504,12 @@ window.nomadEnv = { "align": "left" }, "authors": { - "label": "Authors", "align": "left" }, "results.properties.spectroscopic.spectra.provenance.eels.min_energy": { - "label": "Min energy", "align": "left" }, "results.properties.spectroscopic.spectra.provenance.eels.max_energy": { - "label": "Max energy", "align": "left" }, "entry_name": { @@ -1583,23 +1517,18 @@ window.nomadEnv = { "align": "left" }, "entry_type": { - "label": "Entry type", "align": "left" }, "mainfile": { - "label": "Mainfile", "align": "left" }, "comment": { - "label": "Comment", "align": "left" }, "references": { - "label": "References", "align": "left" }, "datasets": { - "label": "Datasets", "align": "left" }, "published": { @@ -1696,7 +1625,7 @@ window.nomadEnv = { "columns": { "options": { "results.material.chemical_formula_descriptive": { - "label": "Descriptive Formula", + "label": "Descriptive formula", "align": "left" }, "results.properties.optoelectronic.solar_cell.efficiency": { @@ -1708,7 +1637,6 @@ window.nomadEnv = { } }, "results.properties.optoelectronic.solar_cell.open_circuit_voltage": { - "label": "Open circuit voltage", "align": "left", "unit": "V", "format": { @@ -1717,7 +1645,6 @@ window.nomadEnv = { } }, "results.properties.optoelectronic.solar_cell.short_circuit_current_density": { - "label": "Short circuit current density", "align": "left", "unit": "A/m**2", "format": { @@ -1726,7 +1653,6 @@ window.nomadEnv = { } }, "results.properties.optoelectronic.solar_cell.fill_factor": { - "label": "Fill factor", "align": "left", "format": { "decimals": 3, @@ -1734,7 +1660,6 @@ window.nomadEnv = { } }, "references": { - "label": "References", "align": "left" }, "results.material.chemical_formula_hill": { @@ -1742,7 +1667,6 @@ window.nomadEnv = { "align": "left" }, "results.material.structural_type": { - "label": "Dimensionality", "align": "left" }, "results.properties.optoelectronic.solar_cell.illumination_intensity": { @@ -1755,23 +1679,18 @@ window.nomadEnv = { } }, "results.eln.lab_ids": { - "label": "Lab IDs", "align": "left" }, "results.eln.sections": { - "label": "Sections", "align": "left" }, "results.eln.methods": { - "label": "Methods", "align": "left" }, "results.eln.tags": { - "label": "Tags", "align": "left" }, "results.eln.instruments": { - "label": "Instruments", "align": "left" }, "entry_name": { @@ -1779,11 +1698,9 @@ window.nomadEnv = { "align": "left" }, "entry_type": { - "label": "Entry type", "align": "left" }, "mainfile": { - "label": "Mainfile", "align": "left" }, "upload_create_time": { @@ -1791,15 +1708,12 @@ window.nomadEnv = { "align": "left" }, "authors": { - "label": "Authors", "align": "left" }, "comment": { - "label": "Comment", "align": "left" }, "datasets": { - "label": "Datasets", "align": "left" }, "published": { @@ -2206,6 +2120,7 @@ window.nomadEnv = { "showinput": true }, { + "title": "Band gap", "type": "histogram", "layout": { "lg": { @@ -2381,7 +2296,6 @@ window.nomadEnv = { "align": "left" }, "mainfile": { - "label": "Mainfile", "align": "left" }, "upload_create_time": { @@ -2389,15 +2303,12 @@ window.nomadEnv = { "align": "left" }, "authors": { - "label": "Authors", "align": "left" }, "comment": { - "label": "Comment", "align": "left" }, "datasets": { - "label": "Datasets", "align": "left" }, "published": { @@ -2518,6 +2429,7 @@ window.nomadEnv = { "scale": "linear" }, { + "title": "SBU type", "type": "terms", "layout": { "lg": { @@ -2791,15 +2703,12 @@ window.nomadEnv = { "columns": { "options": { "results.material.elements": { - "label": "Elements", "align": "left" }, "results.properties.catalytic.catalyst_synthesis.catalyst_type": { - "label": "Catalyst Type", "align": "left" }, "results.properties.catalytic.catalyst_synthesis.catalyst_name": { - "label": "Catalyst Name", "align": "left" }, "results.properties.catalytic.catalyst_synthesis.preparation_method": { @@ -2807,7 +2716,7 @@ window.nomadEnv = { "align": "left" }, "results.properties.catalytic.catalyst_characterization.surface_area": { - "label": "Surface Area (m^2/g)", + "label": "Surface area (m^2/g)", "align": "left", "format": { "decimals": 2, @@ -2815,11 +2724,11 @@ window.nomadEnv = { } }, "results.properties.catalytic.reaction.name": { - "label": "Reaction Name", + "label": "Reaction name", "align": "left" }, "results.properties.catalytic.reaction.type": { - "label": "Reaction Class", + "label": "Reaction class", "align": "left" }, "results.properties.catalytic.reaction.reactants.name": { @@ -2831,7 +2740,6 @@ window.nomadEnv = { "align": "left" }, "references": { - "label": "References", "align": "left" }, "results.material.chemical_formula_hill": { @@ -2839,27 +2747,21 @@ window.nomadEnv = { "align": "left" }, "results.material.structural_type": { - "label": "Dimensionality", "align": "left" }, "results.eln.lab_ids": { - "label": "Lab IDs", "align": "left" }, "results.eln.sections": { - "label": "Sections", "align": "left" }, "results.eln.methods": { - "label": "Methods", "align": "left" }, "results.eln.tags": { - "label": "Tags", "align": "left" }, "results.eln.instruments": { - "label": "Instruments", "align": "left" }, "entry_name": { @@ -2867,11 +2769,9 @@ window.nomadEnv = { "align": "left" }, "entry_type": { - "label": "Entry type", "align": "left" }, "mainfile": { - "label": "Mainfile", "align": "left" }, "upload_create_time": { @@ -2879,15 +2779,12 @@ window.nomadEnv = { "align": "left" }, "authors": { - "label": "Authors", "align": "left" }, "comment": { - "label": "Comment", "align": "left" }, "datasets": { - "label": "Datasets", "align": "left" }, "published": { diff --git a/nomad/cli/dev.py b/nomad/cli/dev.py index 3d6baf264c..a476ddd9ae 100644 --- a/nomad/cli/dev.py +++ b/nomad/cli/dev.py @@ -192,6 +192,8 @@ def _generate_search_quantities(): @dev.command(help='Generates a JSON with all search quantities.') def search_quantities(): + from nomad.datamodel import all_metainfo_packages + all_metainfo_packages() print(json.dumps(_generate_search_quantities(), indent=2)) diff --git a/nomad/config/defaults.yaml b/nomad/config/defaults.yaml index 1b932c723e..0822584cf7 100644 --- a/nomad/config/defaults.yaml +++ b/nomad/config/defaults.yaml @@ -744,66 +744,36 @@ ui: - upload_create_time - authors options: - entry_name: - label: Name - results.material.chemical_formula_hill: - label: Formula - entry_type: - label: Entry type - entry_create_time: - label: Entry creation time - upload_name: - label: Upload name - upload_id: - label: Upload id - upload_create_time: - label: Upload time - authors: - label: Authors - results.method.method_name: - label: Method name - results.method.simulation.program_name: - label: Program name - results.method.simulation.dft.xc_functional_type: - label: XC Functional Type - results.method.simulation.precision.apw_cutoff: - label: APW Cutoff - results.method.simulation.precision.basis_set: - label: Basis Set - results.method.simulation.precision.k_line_density: - label: k-line Density - results.method.simulation.precision.native_tier: - label: Code-specific tier - results.method.simulation.precision.planewave_cutoff: - label: Plane-wave cutoff - results.material.structural_type: - label: Dimensionality - results.material.symmetry.crystal_system: - label: Crystal system - results.material.symmetry.space_group_symbol: - label: Space group symbol - results.material.symmetry.space_group_number: - label: Space group number - results.eln.lab_ids: - label: Lab IDs - results.eln.sections: - label: Sections - results.eln.methods: - label: Methods - results.eln.tags: - label: Tags - results.eln.instruments: - label: Instruments - mainfile: - label: Mainfile - comment: - label: Comment - references: - label: References - datasets: - label: Datasets - published: - label: Access + entry_name: {label: Name} + results.material.chemical_formula_hill: {label: Formula} + entry_type: {} + entry_create_time: {} + upload_name: {} + upload_id: {} + upload_create_time: {label: Upload time} + authors: {} + results.method.method_name: {} + results.method.simulation.program_name: {} + results.method.simulation.dft.xc_functional_type: {} + results.method.simulation.precision.apw_cutoff: {} + results.method.simulation.precision.basis_set: {} + results.method.simulation.precision.k_line_density: {} + results.method.simulation.precision.native_tier: {} + results.method.simulation.precision.planewave_cutoff: {} + results.material.structural_type: {} + results.material.symmetry.crystal_system: {} + results.material.symmetry.space_group_symbol: {} + results.material.symmetry.space_group_number: {} + results.eln.lab_ids: {} + results.eln.sections: {} + results.eln.methods: {} + results.eln.tags: {} + results.eln.instruments: {} + mainfile: {} + comment: {} + references: {} + datasets: {} + published: {label: Access} filter_menus: options: material: @@ -901,48 +871,27 @@ ui: - upload_create_time - authors options: - results.material.chemical_formula_hill: - label: Formula - results.method.simulation.program_name: - label: Program name - results.method.method_name: - label: Method name - results.method.simulation.dft.xc_functional_type: - label: Jacob's ladder - upload_create_time: - label: Upload time - authors: - label: Authors - results.method.simulation.precision.apw_cutoff: - label: APW Cutoff - results.method.simulation.precision.basis_set: - label: Basis Set - results.method.simulation.precision.k_line_density: - label: k-line Density - results.method.simulation.precision.native_tier: - label: Code-specific tier - results.method.simulation.precision.planewave_cutoff: - label: Plane-wave cutoff - results.material.structural_type: - label: Dimensionality - results.material.symmetry.crystal_system: - label: Crystal system - results.material.symmetry.space_group_symbol: - label: Space group symbol - results.material.symmetry.space_group_number: - label: Space group number - entry_name: - label: Name - mainfile: - label: Mainfile - comment: - label: Comment - references: - label: References - datasets: - label: Datasets - published: - label: Access + results.material.chemical_formula_hill: {label: Formula} + results.method.simulation.program_name: {} + results.method.method_name: {} + results.method.simulation.dft.xc_functional_type: {} + upload_create_time: {label: Upload time} + authors: {} + results.method.simulation.precision.apw_cutoff: {} + results.method.simulation.precision.basis_set: {} + results.method.simulation.precision.k_line_density: {} + results.method.simulation.precision.native_tier: {} + results.method.simulation.precision.planewave_cutoff: {} + results.material.structural_type: {} + results.material.symmetry.crystal_system: {} + results.material.symmetry.space_group_symbol: {} + results.material.symmetry.space_group_number: {} + entry_name: {label: Name} + mainfile: {} + comment: {} + references: {} + datasets: {} + published: {label: Access} filter_menus: options: material: @@ -1102,20 +1051,13 @@ ui: - symmetry.space_group_number - symmetry.crystal_system options: - chemical_formula_hill: - label: Formula - structural_type: - label: Dimensionality - symmetry.structure_name: - label: Structure name - symmetry.space_group_number: - label: Space group number - symmetry.crystal_system: - label: Crystal system - symmetry.space_group_symbol: - label: Space group symbol - material_id: - label: Material ID + chemical_formula_hill: {label: Formula} + structural_type: {} + symmetry.structure_name: {} + symmetry.space_group_number: {} + symmetry.crystal_system: {} + symmetry.space_group_symbol: {} + material_id: {} filter_menus: options: material: @@ -1201,38 +1143,22 @@ ui: - upload_create_time - authors options: - entry_name: - label: Name - entry_type: - label: Entry type - upload_create_time: - label: Upload time - authors: - label: Authors - results.material.chemical_formula_hill: - label: Formula - results.method.method_name: - label: Method name - results.eln.lab_ids: - label: Lab IDs - results.eln.sections: - label: Sections - results.eln.methods: - label: Methods - results.eln.tags: - label: Tags - results.eln.instruments: - label: Instruments - mainfile: - label: Mainfile - comment: - label: Comment - references: - label: References - datasets: - label: Datasets - published: - label: Access + entry_name: {label: Name} + entry_type: {} + upload_create_time: {label: Upload time} + authors: {} + results.material.chemical_formula_hill: {label: Formula} + results.method.method_name: {} + results.eln.lab_ids: {} + results.eln.sections: {} + results.eln.methods: {} + results.eln.tags: {} + results.eln.instruments: {} + mainfile: {} + comment: {} + references: {} + datasets: {} + published: {label: Access} filter_menus: options: material: @@ -1282,34 +1208,20 @@ ui: - upload_create_time - authors options: - results.material.chemical_formula_hill: - label: Formula - results.properties.spectroscopic.spectra.provenance.eels.detector_type: - label: Detector type - results.properties.spectroscopic.spectra.provenance.eels.resolution: - label: Resolution - upload_create_time: - label: Upload time - authors: - label: Authors - results.properties.spectroscopic.spectra.provenance.eels.min_energy: - label: Min energy - results.properties.spectroscopic.spectra.provenance.eels.max_energy: - label: Max energy - entry_name: - label: Name - entry_type: - label: Entry type - mainfile: - label: Mainfile - comment: - label: Comment - references: - label: References - datasets: - label: Datasets - published: - label: Access + results.material.chemical_formula_hill: {label: Formula} + results.properties.spectroscopic.spectra.provenance.eels.detector_type: {} + results.properties.spectroscopic.spectra.provenance.eels.resolution: {} + upload_create_time: {label: Upload time} + authors: {} + results.properties.spectroscopic.spectra.provenance.eels.min_energy: {} + results.properties.spectroscopic.spectra.provenance.eels.max_energy: {} + entry_name: {label: Name} + entry_type: {} + mainfile: {} + comment: {} + references: {} + datasets: {} + published: {label: Access} filter_menus: options: material: @@ -1364,8 +1276,7 @@ ui: - results.properties.optoelectronic.solar_cell.fill_factor - references options: - results.material.chemical_formula_descriptive: - label: Descriptive Formula + results.material.chemical_formula_descriptive: {label: Descriptive formula} results.properties.optoelectronic.solar_cell.efficiency: format: decimals: 2 @@ -1375,57 +1286,39 @@ ui: format: decimals: 3 mode: standard - label: Open circuit voltage unit: V results.properties.optoelectronic.solar_cell.short_circuit_current_density: format: decimals: 3 mode: standard - label: Short circuit current density unit: A/m**2 results.properties.optoelectronic.solar_cell.fill_factor: format: decimals: 3 mode: standard - label: Fill factor - references: - label: References + references: {} results.material.chemical_formula_hill: label: Formula - results.material.structural_type: - label: Dimensionality + results.material.structural_type: {} results.properties.optoelectronic.solar_cell.illumination_intensity: format: decimals: 3 mode: standard label: Illum. intensity unit: W/m**2 - results.eln.lab_ids: - label: Lab IDs - results.eln.sections: - label: Sections - results.eln.methods: - label: Methods - results.eln.tags: - label: Tags - results.eln.instruments: - label: Instruments - entry_name: - label: Name - entry_type: - label: Entry type - mainfile: - label: Mainfile - upload_create_time: - label: Upload time - authors: - label: Authors - comment: - label: Comment - datasets: - label: Datasets - published: - label: Access + results.eln.lab_ids: {} + results.eln.sections: {} + results.eln.methods: {} + results.eln.tags: {} + results.eln.instruments: {} + entry_name: {label: Name} + entry_type: {} + mainfile: {} + upload_create_time: {label: Upload time} + authors: {} + comment: {} + datasets: {} + published: {label: Access} filter_menus: options: material: @@ -1541,6 +1434,7 @@ ui: xxl: {h: 3, minH: 3, minW: 8, w: 8, x: 0, y: 11} nbins: 30 quantity: results.properties.electronic.band_structure_electronic.band_gap.value + title: 'Band gap' scale: 1/4 showinput: false type: histogram @@ -1590,20 +1484,13 @@ ui: - mainfile - authors options: - results.material.chemical_formula_iupac: - label: Formula - mainfile: - label: Mainfile - upload_create_time: - label: Upload time - authors: - label: Authors - comment: - label: Comment - datasets: - label: Datasets - published: - label: Access + results.material.chemical_formula_iupac: {label: Formula} + mainfile: {} + upload_create_time: {label: Upload time} + authors: {} + comment: {} + datasets: {} + published: {label: Access} filter_menus: options: material: @@ -1643,6 +1530,7 @@ ui: xl: {h: 9, minH: 3, minW: 3, w: 11, x: 19, y: 0} xxl: {h: 10, minH: 3, minW: 3, w: 11, x: 25, y: 0} quantity: results.material.topology.sbu_type + title: 'SBU type' scale: linear showinput: true type: terms @@ -1722,59 +1610,35 @@ ui: - results.properties.catalytic.catalyst_synthesis.preparation_method - results.properties.catalytic.catalyst_characterization.surface_area options: - results.material.elements: - label: Elements - results.properties.catalytic.catalyst_synthesis.catalyst_type: - label: Catalyst Type - results.properties.catalytic.catalyst_synthesis.catalyst_name: - label: Catalyst Name - results.properties.catalytic.catalyst_synthesis.preparation_method: - label: Preparation + results.material.elements: {} + results.properties.catalytic.catalyst_synthesis.catalyst_type: {} + results.properties.catalytic.catalyst_synthesis.catalyst_name: {} + results.properties.catalytic.catalyst_synthesis.preparation_method: {label: Preparation} results.properties.catalytic.catalyst_characterization.surface_area: format: decimals: 2 mode: standard - label: Surface Area (m^2/g) - results.properties.catalytic.reaction.name: - label: Reaction Name - results.properties.catalytic.reaction.type: - label: Reaction Class - results.properties.catalytic.reaction.reactants.name: - label: Reactants - results.properties.catalytic.reaction.products.name: - label: Products - references: - label: References - results.material.chemical_formula_hill: - label: Formula - results.material.structural_type: - label: Dimensionality - results.eln.lab_ids: - label: Lab IDs - results.eln.sections: - label: Sections - results.eln.methods: - label: Methods - results.eln.tags: - label: Tags - results.eln.instruments: - label: Instruments - entry_name: - label: Name - entry_type: - label: Entry type - mainfile: - label: Mainfile - upload_create_time: - label: Upload time - authors: - label: Authors - comment: - label: Comment - datasets: - label: Datasets - published: - label: Access + label: Surface area (m^2/g) + results.properties.catalytic.reaction.name: {label: Reaction name} + results.properties.catalytic.reaction.type: {label: Reaction class} + results.properties.catalytic.reaction.reactants.name: {label: Reactants} + results.properties.catalytic.reaction.products.name: {label: Products} + references: {} + results.material.chemical_formula_hill: {label: Formula} + results.material.structural_type: {} + results.eln.lab_ids: {} + results.eln.sections: {} + results.eln.methods: {} + results.eln.tags: {} + results.eln.instruments: {} + entry_name: {label: Name} + entry_type: {} + mainfile: {} + upload_create_time: {label: Upload time} + authors: {} + comment: {} + datasets: {} + published: {label: Access} filter_menus: options: material: diff --git a/nomad/config/models/ui.py b/nomad/config/models/ui.py index 41ac56c7da..7ff0ffcbbd 100644 --- a/nomad/config/models/ui.py +++ b/nomad/config/models/ui.py @@ -442,6 +442,9 @@ class Markers(ConfigBaseModel): class Widget(ConfigBaseModel): """Common configuration for all widgets.""" + title: Optional[str] = Field( + description='Custom widget title. If not specified, a widget-specific default title is used.' + ) type: str = Field(description='Used to identify the widget type.') layout: Dict[BreakpointEnum, Layout] = Field( description=""" diff --git a/nomad/datamodel/metainfo/annotations.py b/nomad/datamodel/metainfo/annotations.py index 684e1e80ce..555d8aeaf7 100644 --- a/nomad/datamodel/metainfo/annotations.py +++ b/nomad/datamodel/metainfo/annotations.py @@ -275,13 +275,19 @@ class ELNAnnotation(AnnotationModel): ) label: str = Field( - None, description='Custom label for the quantity shown on the form field.' + None, + description=""" + [Deprecated] ELN label annotation has been deprecated and it is advised to + utilize display annotation instead. Custom label for the quantity shown on the form field. + It is recommended to adhere to the convention of using lowercase letters for the label, + except for abbreviations which could be capitalized. + """, ) props: Dict[str, Any] = Field( None, description=""" - A dictionary with additional props that are passed to the editcomponent. + A dictionary with additional props that are passed to the edit component. """, ) diff --git a/nomad/datamodel/metainfo/basesections.py b/nomad/datamodel/metainfo/basesections.py index 75ffe373a8..a0f7711a20 100644 --- a/nomad/datamodel/metainfo/basesections.py +++ b/nomad/datamodel/metainfo/basesections.py @@ -318,7 +318,7 @@ class ActivityStep(ArchiveSection): """, a_eln=ELNAnnotation( component='StringEditQuantity', - label='Step name', + label='step name', ), ) start_time = Quantity( @@ -327,7 +327,7 @@ class ActivityStep(ArchiveSection): Optionally, the starting time of the activity step. If omitted, it is assumed to follow directly after the previous step. """, - a_eln=ELNAnnotation(component='DateTimeEditQuantity', label='Starting time'), + a_eln=ELNAnnotation(component='DateTimeEditQuantity', label='starting time'), ) comment = Quantity( type=str, @@ -360,7 +360,7 @@ class Activity(BaseSection): datetime = Quantity( type=Datetime, description='The date and time when this activity was started.', - a_eln=dict(component='DateTimeEditQuantity', label='Starting Time'), + a_eln=dict(component='DateTimeEditQuantity', label='starting Time'), ) method = Quantity( type=str, @@ -418,7 +418,7 @@ class SectionReference(ArchiveSection): description='A reference to a NOMAD archive section.', a_eln=ELNAnnotation( component='ReferenceEditQuantity', - label='Section Reference', + label='section reference', ), ) @@ -433,7 +433,7 @@ class EntityReference(SectionReference): description='A reference to a NOMAD `Entity` entry.', a_eln=ELNAnnotation( component='ReferenceEditQuantity', - label='Entity Reference', + label='entity reference', ), ) lab_id = Quantity( @@ -503,7 +503,7 @@ class ExperimentStep(ActivityStep): """, a_eln=ELNAnnotation( component='StringEditQuantity', - label='Activity ID', + label='activity ID', ), ) @@ -782,7 +782,7 @@ class InstrumentReference(EntityReference): description='A reference to a NOMAD `Instrument` entry.', a_eln=ELNAnnotation( component='ReferenceEditQuantity', - label='Instrument Reference', + label='instrument reference', ), ) @@ -795,7 +795,7 @@ class Component(ArchiveSection): name = Quantity( type=str, description='A short name for the component.', - a_eln=dict(component='StringEditQuantity', label='Component label'), + a_eln=dict(component='StringEditQuantity', label='component label'), ) mass = Quantity( type=np.float64, @@ -845,7 +845,7 @@ class PureSubstanceSection(ArchiveSection): name = Quantity( type=str, description='A short name for the substance.', - a_eln=dict(component='StringEditQuantity', label='Substance name'), + a_eln=dict(component='StringEditQuantity', label='substance name'), ) iupac_name = Quantity( type=str, @@ -1119,7 +1119,7 @@ class CompositeSystemReference(EntityReference): description='A reference to a NOMAD `CompositeSystem` entry.', a_eln=ELNAnnotation( component='ReferenceEditQuantity', - label='Composite System Reference', + label='composite system reference', ), ) @@ -1160,7 +1160,7 @@ class Process(Activity): end_time = Quantity( type=Datetime, description='The date and time when this process was finished.', - a_eln=dict(component='DateTimeEditQuantity', label='Ending Time'), + a_eln=dict(component='DateTimeEditQuantity', label='ending time'), ) steps = SubSection( section_def=ProcessStep, @@ -1348,14 +1348,14 @@ class PureSubstance(System): name = Quantity( type=str, description='The name of the substance entry.', - a_eln=dict(component='StringEditQuantity', label='Substance name'), + a_eln=dict(component='StringEditQuantity', label='substance name'), ) lab_id = Quantity( type=str, description=""" A human human readable substance ID that is at least unique for the lab. """, - a_eln=dict(component='StringEditQuantity', label='Substance ID'), + a_eln=dict(component='StringEditQuantity', label='substance ID'), ) description = Quantity( type=str, @@ -1365,7 +1365,7 @@ class PureSubstance(System): """, a_eln=dict( component='RichTextEditQuantity', - label='Detailed substance description', + label='detailed substance description', ), ) pure_substance = SubSection( @@ -1650,7 +1650,7 @@ class CASPureSubstanceSection(PureSubstanceSection): type=str, description='CAS image.', a_eln=dict(component='FileEditQuantity'), - a_browser=dict(adaptor='RawFileAdaptor', label='Image of substance'), + a_browser=dict(adaptor='RawFileAdaptor', label='image of substance'), ) cas_experimental_properties = SubSection( section_def=CASExperimentalProperty, diff --git a/nomad/datamodel/metainfo/eln/__init__.py b/nomad/datamodel/metainfo/eln/__init__.py index 4f2cd88f77..993b58d697 100644 --- a/nomad/datamodel/metainfo/eln/__init__.py +++ b/nomad/datamodel/metainfo/eln/__init__.py @@ -106,7 +106,7 @@ class ElnBaseSection(ArchiveSection): name = Quantity( type=str, description='A short human readable and descriptive name.', - a_eln=dict(component='StringEditQuantity', label='Short name'), + a_eln=dict(component='StringEditQuantity', label='short name'), ) datetime = Quantity( @@ -624,7 +624,7 @@ class Substance(System): name = Quantity( type=str, description='The name of the substance entry.', - a_eln=dict(component='StringEditQuantity', label='Substance name'), + a_eln=dict(component='StringEditQuantity', label='substance name'), ) lab_id = Quantity( @@ -632,7 +632,7 @@ class Substance(System): description=""" A human human readable substance ID that is at least unique for the lab. """, - a_eln=dict(component='StringEditQuantity', label='Substance ID'), + a_eln=dict(component='StringEditQuantity', label='substance ID'), ) cas_uri = Quantity( @@ -657,7 +657,7 @@ class Substance(System): type=str, description='CAS image.', a_eln=dict(component='FileEditQuantity'), - a_browser=dict(adaptor='RawFileAdaptor', label='Image of substance'), + a_browser=dict(adaptor='RawFileAdaptor', label='image of substance'), ) inchi = Quantity( @@ -708,7 +708,7 @@ class Substance(System): by the other quantities and subsections. """, a_eln=dict( - component='RichTextEditQuantity', label='Detailed substance description' + component='RichTextEditQuantity', label='detailed substance description' ), ) -- GitLab