Skip to content

Commit b767f85

Browse files
committed
Fixed issue with resolving units when using full storage
Changelog: Fixed
1 parent 14404bc commit b767f85

File tree

5 files changed

+110
-73
lines changed

5 files changed

+110
-73
lines changed

gui/src/components/archive/ArchiveBrowser.js

+64-61
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import SaveIcon from '@material-ui/icons/Save'
3333
import { Alert } from '@material-ui/lab'
3434
import classNames from 'classnames'
3535
import DOMPurify from 'dompurify'
36-
import { isArray, isNaN, partition, range } from 'lodash'
36+
import { isArray, isNaN, isPlainObject, partition, range } from 'lodash'
3737
import { complex, format } from 'mathjs'
3838
import PropTypes from 'prop-types'
3939
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
@@ -60,6 +60,7 @@ import { useErrors } from '../errors'
6060
import Markdown from '../Markdown'
6161
import { EntryButton } from '../nav/Routes'
6262
import { Quantity as Q } from '../units/Quantity'
63+
import { Unit } from '../units/Unit'
6364
import { useDisplayUnit } from '../units/useDisplayUnit'
6465
import H5Web from '../visualization/H5Web'
6566
import Pagination from '../visualization/Pagination'
@@ -673,11 +674,13 @@ class QuantityAdaptor extends ArchiveAdaptor {
673674
}
674675

675676
render() {
676-
if (quantityUsesFullStorage(this.def)) {
677-
return <FullStorageQuantity value={this.obj} def={this.def}/>
678-
} else {
679-
return <Quantity value={this.obj} def={this.def}/>
680-
}
677+
return <Quantity value={this.obj} def={this.def}>
678+
{this.obj?.m_attributes?.length > 0 && <Compartment title="attributes">
679+
{Object.keys(this.obj?.m_attributes).map(key => (
680+
<Item key={key} itemKey={key}>{key}</Item>
681+
))}
682+
</Compartment>}
683+
</Quantity>
681684
}
682685
}
683686

@@ -694,7 +697,7 @@ const convertComplexArray = (real, imag) => {
694697
}
695698

696699
export function QuantityItemPreview({value, def}) {
697-
const displayUnit = useDisplayUnit(def)
700+
let {finalValue, displayUnit, storageUnit} = useQuantityData(value, def)
698701

699702
if (isReference(def)) {
700703
return <Box component="span" fontStyle="italic">
@@ -720,14 +723,14 @@ export function QuantityItemPreview({value, def}) {
720723
const dimensions = []
721724
let typeLabel = 'unknown'
722725
try {
723-
let current = value.re || value.im || value
726+
let current = finalValue.re || finalValue.im || finalValue
724727
for (let i = 0; i < def.shape.length; i++) {
725728
dimensions.push(current.length)
726729
current = current[0]
727730
}
728731
if (def.type.type_kind === 'python') {
729732
typeLabel = 'list'
730-
} else if (typeof value === 'string') {
733+
} else if (typeof finalValue === 'string') {
731734
typeLabel = 'HDF5 array'
732735
dimensions.length = 0
733736
} else {
@@ -752,17 +755,13 @@ export function QuantityItemPreview({value, def}) {
752755
</Typography>
753756
</Box>
754757
} else {
755-
let finalValue
756758
if (def.type.type_data === 'nomad.metainfo.metainfo._Datetime' || def.type.type_data === 'nomad.metainfo.data_type.Datetime') {
757-
finalValue = formatTimestamp(value)
759+
finalValue = formatTimestamp(finalValue)
758760
} else if (def.type.type_data.startsWith?.('complex')) {
759-
finalValue = convertComplexArray(value.re, value.im)
760-
} else {
761-
finalValue = value
761+
finalValue = convertComplexArray(finalValue.re, finalValue.im)
762762
}
763-
764763
if (displayUnit) {
765-
finalValue = new Q(finalValue, def.unit).to(displayUnit).value()
764+
finalValue = new Q(finalValue, storageUnit).to(displayUnit).value()
766765
}
767766
return <Box component="span" whiteSpace="nowarp">
768767
<Number component="span" variant="body1" value={finalValue} exp={8}/>
@@ -776,35 +775,57 @@ QuantityItemPreview.propTypes = ({
776775
def: PropTypes.object.isRequired
777776
})
778777

778+
/**
779+
* Hook for getting the final value and units for a quantity. Also supports
780+
* quantities using full storage.
781+
*
782+
* @param {*} data Value of the quantity
783+
* @param {*} def Defintion of the quantity
784+
* @returns Object containing the final value, storage unit and display unit.
785+
*/
786+
function useQuantityData(data, def) {
787+
let storageUnit = def.unit
788+
let displayUnit = useDisplayUnit(def)
789+
let finalValue = data
790+
if (quantityUsesFullStorage(def) && isPlainObject(data)) {
791+
displayUnit = data?.m_unit && new Unit(data.m_unit)
792+
storageUnit = data?.m_original_unit
793+
finalValue = data?.m_value
794+
}
795+
return {finalValue, displayUnit, storageUnit}
796+
}
797+
779798
export const QuantityValue = React.memo(function QuantityValue({value, def}) {
780799
const {uploadId} = useEntryStore() || {}
781-
const displayUnit = useDisplayUnit(def)
800+
let {finalValue, displayUnit, storageUnit} = useQuantityData(value, def)
782801

783-
const getRenderValue = useCallback(value => {
784-
let finalValue
802+
const getRenderValue = useCallback((value) => {
803+
let finalValue, finalUnit
785804
if (def.type.type_data === 'nomad.metainfo.metainfo._Datetime' || def.type.type_data === 'nomad.metainfo.data_type.Datetime') {
786805
finalValue = formatTimestamp(value)
787806
} else if (def.type.type_data.startsWith?.('complex')) {
788807
finalValue = convertComplexArray(value.re, value.im)
789808
} else {
790809
finalValue = value
791810
}
792-
let finalUnit
793-
if (def.unit && typeof finalValue !== 'string') {
794-
const systemUnitQ = new Q(finalValue, def.unit).to(displayUnit)
811+
812+
if (typeof finalValue !== 'string' && storageUnit && displayUnit) {
813+
const systemUnitQ = new Q(finalValue, storageUnit).to(displayUnit)
795814
finalValue = systemUnitQ.value()
796815
finalUnit = systemUnitQ.label()
797816
}
817+
798818
return [finalValue, finalUnit]
799-
}, [def, displayUnit])
819+
}, [def, storageUnit, displayUnit])
800820

801821
const isMathValue = (def.type.type_kind === 'numpy' || def.type.type_kind === 'python') && typeof value !== 'string'
802822
if (isMathValue) {
803-
const [finalValue, finalUnit] = getRenderValue(value)
823+
const [renderValue, finalUnit] = getRenderValue(finalValue)
804824
if (def.shape.length > 0) {
825+
console.log(renderValue)
805826
return <Box textAlign="center">
806827
<Matrix
807-
values={finalValue}
828+
values={renderValue}
808829
shape={def.shape}
809830
invert={def.shape.length === 1}
810831
type={def.type.type_data}
@@ -818,54 +839,53 @@ export const QuantityValue = React.memo(function QuantityValue({value, def}) {
818839
{finalUnit && <Typography noWrap>{finalUnit}</Typography>}
819840
</Box>
820841
} else {
821-
return <Number value={finalValue} exp={16} variant="body1" unit={finalUnit}/>
842+
return <Number value={renderValue} exp={16} variant="body1" unit={finalUnit}/>
822843
}
823844
} else if (def.m_annotations?.browser?.[0]?.render_value === 'HtmlValue' || def.m_annotations?.eln?.[0]?.component === 'RichTextEditQuantity') {
824-
const html = DOMPurify.sanitize(value)
845+
const html = DOMPurify.sanitize(finalValue)
825846
return <div dangerouslySetInnerHTML={{__html: html}}/>
826847
} else if (def.type?.type_data === 'nomad.metainfo.metainfo._JSON' || def.type?.type_data === 'nomad.metainfo.data_type.JSON') {
827848
return <ReactJson
828849
name="value"
829-
src={value}
850+
src={finalValue}
830851
enableClipboard={false}
831852
collapsed={2}
832853
displayObjectSize={false}
833854
/>
834855
} else {
835856
if (def.type.type_data.startsWith?.('complex')) {
836-
value = convertComplexArray(value.re, value.im)
857+
finalValue = convertComplexArray(finalValue.re, finalValue.im)
837858

838-
return Array.isArray(value)
859+
return Array.isArray(finalValue)
839860
? <ul style={{margin: 0}}>
840-
{value.map((value, index) => <li key={index}><Typography>{value}</Typography></li>)}
861+
{finalValue.map((value, index) => <li key={index}><Typography>{value}</Typography></li>)}
841862
</ul>
842-
: <Typography>{value}</Typography>
843-
} else if (Array.isArray(value)) {
863+
: <Typography>{finalValue}</Typography>
864+
} else if (Array.isArray(finalValue)) {
844865
return <ul style={{margin: 0}}>
845-
{value.map((value, index) => {
846-
const [finalValue] = getRenderValue(value)
866+
{finalValue.map((value, index) => {
867+
const [renderValue] = getRenderValue(value)
847868
return <li key={index}>
848-
<Typography>{typeof finalValue === 'object' ? JSON.stringify(finalValue) : finalValue?.toString()}</Typography>
869+
<Typography>{typeof renderValue === 'object' ? JSON.stringify(renderValue) : renderValue?.toString()}</Typography>
849870
</li>
850871
})}
851872
</ul>
852873
} else if (def.type?.type_data === 'nomad.datamodel.hdf5.HDF5Dataset' || def.type?.type_data === 'nomad.datamodel.hdf5.HDF5Reference') {
853-
const {h5UploadId, h5File, h5Source, h5Path} = matchH5Path(value)
874+
const {h5UploadId, h5File, h5Source, h5Path} = matchH5Path(finalValue)
854875
return <Compartment title='hdf5'>
855876
<H5Web upload_id={h5UploadId || uploadId} filename={h5File} initialPath={h5Path} source={h5Source} sidebarOpen={false}></H5Web>
856877
</Compartment>
857878
} else if (def?.type?.type_kind === 'custom' && def?.type?.type_data === 'nomad.datamodel.data.Query') {
858-
return <Query value={value} def={def}/>
879+
return <Query value={finalValue} def={def}/>
859880
} else {
860-
const [finalValue] = getRenderValue(value)
861-
return <Typography>{typeof finalValue === 'object' ? JSON.stringify(finalValue) : finalValue?.toString()}</Typography>
881+
const [renderValue] = getRenderValue(finalValue)
882+
return <Typography>{typeof renderValue === 'object' ? JSON.stringify(renderValue) : renderValue?.toString()}</Typography>
862883
}
863884
}
864885
})
865886
QuantityValue.propTypes = ({
866887
value: PropTypes.any,
867-
def: PropTypes.object.isRequired,
868-
unit: PropTypes.string
888+
def: PropTypes.object.isRequired
869889
})
870890

871891
const InheritingSections = React.memo(function InheritingSections({def, section, lane}) {
@@ -1072,7 +1092,7 @@ export function Section({section, def, property, parentRelation, sectionIsEditab
10721092
const storage = section[quantityDef.name] || {}
10731093
return <React.Fragment key={key}>
10741094
{Object.keys(storage).map(quantityName =>
1075-
renderQuantityItem(key, quantityName, quantityDef, storage[quantityName]?.m_value, disabled)
1095+
renderQuantityItem(key, quantityName, quantityDef, storage[quantityName], disabled)
10761096
)}
10771097
</React.Fragment>
10781098
} else {
@@ -1623,23 +1643,7 @@ SectionPlots.propTypes = {
16231643
entryId: PropTypes.string
16241644
}
16251645

1626-
function FullStorageQuantity({value, def}) {
1627-
const attributes = value.m_attributes || {}
1628-
return <Quantity value={value.m_value} def={def} unit={value.m_unit}>
1629-
{Object.keys(attributes).length > 0 && <Compartment title="attributes">
1630-
{Object.keys(attributes).map(key => (
1631-
<Item key={key} itemKey={key}>{key}</Item>
1632-
))}
1633-
</Compartment>}
1634-
</Quantity>
1635-
}
1636-
1637-
FullStorageQuantity.propTypes = ({
1638-
value: PropTypes.any,
1639-
def: PropTypes.object.isRequired
1640-
})
1641-
1642-
function Quantity({value, def, unit, children}) {
1646+
function Quantity({value, def, children}) {
16431647
const {prev} = useLane()
16441648
return <Content>
16451649
<ArchiveTitle def={def} data={value} kindLabel="value"/>
@@ -1657,7 +1661,6 @@ function Quantity({value, def, unit, children}) {
16571661
<QuantityValue
16581662
value={value}
16591663
def={def}
1660-
unit={unit}
16611664
/>
16621665
</Compartment>
16631666
{children}

gui/src/components/archive/Quantity.spec.js

+30-2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ test.each([
6767
undefined,
6868
'mm',
6969
'3500 mm'
70+
],
71+
[
72+
'full storage',
73+
{
74+
m_value: 3.5,
75+
m_unit: 'm',
76+
m_original_unit: 'm'
77+
},
78+
undefined,
79+
undefined,
80+
'm',
81+
'3.50000 m'
7082
]
7183
])('Test QuantityItemPreview %s', async (name, value, unit, displayUnit, elnUnit, expected) => {
7284
const def = {
@@ -85,7 +97,7 @@ test.each([
8597

8698
render(
8799
<QuantityItemPreview
88-
def={{name: 'value1', shape: [], type: {type_kind: 'python', type_data: 'float'}, ...def}}
100+
def={{name: 'value1', shape: [], type: {type_kind: 'python', type_data: 'float'}, variable: !!value?.m_value, ...def}}
89101
value={value}
90102
/>
91103
)
@@ -182,6 +194,22 @@ describe("Test QuantityValue", () => {
182194
false,
183195
'(1)',
184196
'mm'
197+
],
198+
[
199+
'full storage',
200+
{
201+
m_value: [3.5],
202+
m_unit: 'm',
203+
m_original_unit: 'm'
204+
},
205+
[1],
206+
undefined,
207+
undefined,
208+
undefined,
209+
'3.50000',
210+
false,
211+
'(1)',
212+
'm'
185213
]
186214
])('%s', async (name, value, shape, unit, displayUnit, elnUnit, expectedValue, scientific, expectedDim, expectedUnit) => {
187215
const def = {
@@ -201,7 +229,7 @@ describe("Test QuantityValue", () => {
201229

202230
const screen = render(
203231
<QuantityValue
204-
def={{name: 'value1', type: {type_kind: 'python', type_data: 'float'}, ...def}}
232+
def={{name: 'value1', type: {type_kind: 'python', type_data: 'float'}, variable: !!value?.m_value, ...def}}
205233
value={value}
206234
/>
207235
)

gui/src/components/units/useDisplayUnit.js

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import {Unit} from "./Unit"
44
import {useUnitContext} from "./UnitContext"
55
import {getFieldProps} from "../editQuantity/StringEditQuantity"
66

7+
/**
8+
* Used to retrieve the unit to use for displaying a quantity.
9+
*
10+
* @param {*} quantityDef Definition for the quantity
11+
* @returns {Unit} The unit to use for displaying the quantity.
12+
*/
713
export function useDisplayUnit(quantityDef) {
814
const {units} = useUnitContext()
915
const {raiseError} = useErrors()

nomad/metainfo/metainfo.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@
2424
import re
2525
import sys
2626
import warnings
27+
from collections.abc import Callable as TypingCallable
2728
from collections.abc import Iterable
2829
from copy import deepcopy
2930
from functools import wraps
30-
from typing import Any
31-
from collections.abc import Callable as TypingCallable
32-
from typing import Literal, TypeVar, cast
31+
from typing import Any, Literal, TypeVar, cast
3332
from urllib.parse import urlsplit, urlunsplit
3433

3534
import docstring_parser
@@ -63,7 +62,7 @@
6362
to_dict,
6463
)
6564
from nomad.units import ureg as units
66-
from pydantic import ValidationError
65+
from pydantic import TypeAdapter, ValidationError
6766

6867
from .annotation import (
6968
Annotation,
@@ -2759,7 +2758,7 @@ class Definition(MSection):
27592758
Python references, e.g. in `m_def`.
27602759
27612760
variable:
2762-
A boolean that indicates this property as variable parts in its name.
2761+
A boolean that indicates this property has variable parts in its name.
27632762
If this is set to true, all capital letters in the name can be
27642763
replaced with arbitrary strings. However, variable names work similar to
27652764
aliases and can be considered on-demand aliases. Other aliases and the

nomad/metainfo/util.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from typing import Any
2323

2424
import pint
25-
2625
from nomad.metainfo.data_type import Enum
2726
from nomad.units import ureg
2827

@@ -361,13 +360,15 @@ def resolve_variadic_name(definitions: dict, name: str, hint: str | None = None)
361360
candidates = {}
362361
hint_candidates = {}
363362

364-
for definition in definitions:
365-
match_score = get_namefit(name, definition)
363+
for dname, definition in definitions.items():
364+
if not definition.variable: # TODO: also if type does not match
365+
continue
366+
match_score = get_namefit(name, dname)
366367
if match_score >= 0:
367-
candidates[definition] = match_score
368+
candidates[dname] = match_score
368369
# Check if the hint exists in the definition
369370
if hint and hint in definition.all_attributes:
370-
hint_candidates[definition] = match_score
371+
hint_candidates[dname] = match_score
371372

372373
if len(candidates) == 0:
373374
raise ValueError(f'Cannot find a proper definition for name "{name}".')

0 commit comments

Comments
 (0)