Commit 32f9050b authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Refactored some aspects of ems domain. #403

parent 8f325955
Pipeline #81593 passed with stages
in 47 minutes and 14 seconds
...@@ -144,9 +144,6 @@ ...@@ -144,9 +144,6 @@
[submodule "dependencies/optimade-python-tools"] [submodule "dependencies/optimade-python-tools"]
path = dependencies/optimade-python-tools path = dependencies/optimade-python-tools
url = https://github.com/markus1978/optimade-python-tools.git url = https://github.com/markus1978/optimade-python-tools.git
[submodule "dependencies/parsers/eels"]
path = dependencies/parsers/eels
url = https://github.com/markus1978/eels.git
[submodule "dependencies/parsers/namd"] [submodule "dependencies/parsers/namd"]
path = dependencies/parsers/namd path = dependencies/parsers/namd
url = https://gitlab.mpcdf.mpg.de/nomad-lab/parser-namd.git url = https://gitlab.mpcdf.mpg.de/nomad-lab/parser-namd.git
......
Subproject commit c6bf6c2ca01bff208e424b531b8c214ab709b9b8 Subproject commit b9b8f841709736cbfcb3676226a89c06498aa26f
Subproject commit b42d4aae3fe431ed4afbbc738f5e19cec12634bd
Subproject commit 0d67544ea06713b0cf333eeee8d6ef6929a47f5e Subproject commit c4c75068f91f83fb9262eb09576710d11984db44
Subproject commit 0533ef8393da2501226d60f4db9c0d7be32dbf81 Subproject commit aa7fbc09aa3bde6b654b764d21cd62eaf2badc9e
...@@ -360,8 +360,8 @@ import json ...@@ -360,8 +360,8 @@ import json
from .metainfo import m_env from .metainfo import m_env
from nomad.parsing.parser import MatchingParser from nomad.parsing.parser import MatchingParser
from nomad.datamodel.metainfo.general_experimental import section_experiment as msection_experiment from nomad.datamodel.metainfo.common_experimental import section_experiment as msection_experiment
from nomad.datamodel.metainfo.general_experimental import section_data as msection_data from nomad.datamodel.metainfo.common_experimental import section_data as msection_data
from nomad.datamodel.metainfo.general_experimental_method import section_method as msection_method from nomad.datamodel.metainfo.general_experimental_method import section_method as msection_method
from nomad.datamodel.metainfo.general_experimental_sample import section_sample as msection_sample from nomad.datamodel.metainfo.general_experimental_sample import section_sample as msection_sample
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
import EMSVisualizations from './ems/EMSVisualizations' import EMSVisualizations from './ems/EMSVisualizations'
import QCMSEntryOverview from './qcms/QCMSEntryOverview' import QCMSEntryOverview from './qcms/QCMSEntryOverview'
import QCMSEntryCards from './qcms/QCMSEntryCards' import QCMSEntryCards from './qcms/QCMSEntryCards'
import { Link } from '@material-ui/core'
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
...@@ -201,20 +202,32 @@ export const domains = ({ ...@@ -201,20 +202,32 @@ export const domains = ({
*/ */
searchResultColumns: { searchResultColumns: {
'formula': { 'formula': {
label: 'Formula' label: 'Formula',
supportsSort: true
},
'ems.chemical': {
label: 'Chemical',
supportsSort: true
}, },
'ems.method': { 'ems.method': {
label: 'Method' label: 'Method',
supportsSort: true
},
'ems.data_type': {
label: 'Data',
supportsSort: true
}, },
'ems.experiment_location': { 'ems.origin_time': {
label: 'Location' label: 'Date',
supportsSort: true,
render: entry => (entry.ems && entry.ems.origin_time && new Date(entry.ems.origin_time).toLocaleDateString()) || 'unavailable'
}, },
'ems.experiment_time': { 'ems.repository_url': {
label: 'Date/Time', label: 'Source',
render: entry => (entry.ems && entry.ems.experiment_time !== 'unavailable') ? new Date(entry.ems.experiment_time * 1000).toLocaleString() : 'unavailable' render: entry => <Link target="external" href={entry.ems.entry_repository_url}>{entry.ems.repository_url}</Link>
} }
}, },
defaultSearchResultColumns: ['formula', 'ems.method', 'ems.experiment_location', 'ems.experiment_time'], defaultSearchResultColumns: ['formula', 'ems.chemical', 'ems.method', 'ems.data_type', 'ems.origin_time', 'ems.repository_url'],
/** /**
* A component to render the domain specific quantities in the metadata card of * A component to render the domain specific quantities in the metadata card of
* the entry view. Needs to work with props: data (the entry data from the API), * the entry view. Needs to work with props: data (the entry data from the API),
......
...@@ -27,7 +27,7 @@ class EMSEntryCards extends React.Component { ...@@ -27,7 +27,7 @@ class EMSEntryCards extends React.Component {
<CardContent classes={{root: classes.cardContent}}> <CardContent classes={{root: classes.cardContent}}>
<Markdown classes={{root: classes.description}}>{` <Markdown classes={{root: classes.description}}>{`
The data for this experiment is externally stored and managed. Download the raw experiment data: The data for this experiment is externally stored and managed. Download the raw experiment data:
[${data.ems.repository_url}](${data.ems.repository_url}). [${data.ems && data.ems.repository_url}](${data.ems && data.ems.entry_repository_url}).
The meta data describing this experiment in its original format, can be The meta data describing this experiment in its original format, can be
downloaded here directly: downloaded here directly:
......
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Quantity from '../Quantity' import Quantity from '../Quantity'
import { Typography } from '@material-ui/core' import { Typography, Link } from '@material-ui/core'
import { apiBase } from '../../config' import { apiBase } from '../../config'
export default class EMSEntryOverview extends React.Component { export default class EMSEntryOverview extends React.Component {
...@@ -46,6 +46,15 @@ export default class EMSEntryOverview extends React.Component { ...@@ -46,6 +46,15 @@ export default class EMSEntryOverview extends React.Component {
return ( return (
<Quantity column> <Quantity column>
<Quantity quantity="ems.experiment_summary" label="summary" {...this.props} /> <Quantity quantity="ems.experiment_summary" label="summary" {...this.props} />
{this.state.previewBroken
? data.ems.entry_repository_url && <Quantity label="preview" {...this.props}>
<Typography noWrap>
<a target="external" href={ems.entry_repository_url}>visit this entry on the external database</a>
</Typography>
</Quantity>
: <Quantity label="preview" {...this.props}>
<img alt="preview" style={{maxWidth: '100%', height: 'auto'}} src={relative_preview_url} onError={this.handleBrokenPreview}></img>
</Quantity>}
<Quantity row> <Quantity row>
<Quantity column> <Quantity column>
<Quantity row> <Quantity row>
...@@ -56,26 +65,17 @@ export default class EMSEntryOverview extends React.Component { ...@@ -56,26 +65,17 @@ export default class EMSEntryOverview extends React.Component {
</Quantity> </Quantity>
<Quantity quantity="ems.method" label="experimental method" noWrap {...this.props} /> <Quantity quantity="ems.method" label="experimental method" noWrap {...this.props} />
<Quantity quantity="ems.experiment_location" label="experiment location" noWrap {...this.props} /> <Quantity quantity="ems.experiment_location" label="experiment location" noWrap {...this.props} />
<Quantity label="experiment time" {...this.props}> <Quantity label="experiment or experiment publish date" {...this.props}>
<Typography noWrap>{ <Typography noWrap>{
data.ems.experiment_time && data.ems.experiment_time !== 'unavailable' ? new Date(data.ems.experiment_time * 1000).toLocaleString() : 'unavailable' (ems && ems.origin_time && new Date(ems.origin_time).toLocaleDateString()) || 'unavailable'
}</Typography> }</Typography>
</Quantity> </Quantity>
<Quantity label="data" {...this.props}> <Quantity label="data" {...this.props}>
<Typography noWrap> <Typography noWrap>
<a target="external" href={data.ems.repository_url}>{data.ems.repository_name}</a> <Link target="external" href={ems.entry_repository_url}>{ems.repository_url}</Link>
</Typography> </Typography>
</Quantity> </Quantity>
</Quantity> </Quantity>
{this.state.previewBroken
? data.ems.entry_repository_url && <Quantity label="preview" {...this.props}>
<Typography noWrap>
<a target="external" href={data.ems.entry_repository_url}>visit this entry on the external database</a>
</Typography>
</Quantity>
: <Quantity label="preview" {...this.props}>
<img alt="preview" style={{maxWidth: '100%', height: 'auto'}} src={relative_preview_url} onError={this.handleBrokenPreview}></img>
</Quantity>}
</Quantity> </Quantity>
</Quantity> </Quantity>
) )
......
import React from 'react' import React, { useContext, useState, useEffect } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { withStyles, Divider, Card, CardContent, Grid, CardHeader, Typography, Link } from '@material-ui/core' import { Divider, Card, CardContent, Grid, CardHeader, Typography, Link, makeStyles } from '@material-ui/core'
import { withApi } from '../api' import { apiContext } from '../api'
import { compose } from 'recompose'
import ApiDialogButton from '../ApiDialogButton' import ApiDialogButton from '../ApiDialogButton'
// import Structure from '../visualization/Structure' // import Structure from '../visualization/Structure'
import Quantity from '../Quantity' import Quantity from '../Quantity'
...@@ -10,170 +9,152 @@ import { Link as RouterLink } from 'react-router-dom' ...@@ -10,170 +9,152 @@ import { Link as RouterLink } from 'react-router-dom'
import { DOI } from '../search/DatasetList' import { DOI } from '../search/DatasetList'
import { domains } from '../domains' import { domains } from '../domains'
import { EntryPageContent } from './EntryPage' import { EntryPageContent } from './EntryPage'
import { errorContext } from '../errors'
class RepoEntryView extends React.Component { const useStyles = makeStyles(theme => ({
static styles = theme => ({ root: {
root: { marginTop: theme.spacing(2)
marginTop: theme.spacing(2) },
}, error: {
error: { marginTop: theme.spacing(2)
marginTop: theme.spacing(2) },
}, cardContent: {
cardContent: { paddingTop: 0
paddingTop: 0 },
}, entryCards: {
entryCards: { marginTop: theme.spacing(2)
marginTop: theme.spacing(2) },
}, structureViewer: {
structureViewer: { height: '25rem',
height: '25rem', padding: '0'
padding: '0'
}
})
static propTypes = {
classes: PropTypes.object.isRequired,
api: PropTypes.object.isRequired,
raiseError: PropTypes.func.isRequired,
uploadId: PropTypes.string.isRequired,
calcId: PropTypes.string.isRequired
}
static defaultState = {
calcData: null,
doesNotExist: false
} }
}))
state = {...RepoEntryView.defaultState} export default function RepoEntryView({uploadId, calcId}) {
const classes = useStyles()
componentDidMount() { const {api} = useContext(apiContext)
this.update() const {raiseError} = useContext(errorContext)
} const [state, setState] = useState({calcData: null, doesNotExist: false})
componentDidUpdate(prevProps) { useEffect(() => {
if (prevProps.api !== this.props.api || setState({calcData: null, doesNotExist: false})
prevProps.uploadId !== this.props.uploadId || }, [setState, uploadId, calcId])
prevProps.calcId !== this.props.calcId) {
this.setState({...RepoEntryView.defaultState})
this.update()
}
}
update() { useEffect(() => {
const {uploadId, calcId} = this.props api.repo(uploadId, calcId).then(data => {
this.props.api.repo(uploadId, calcId).then(data => { setState({calcData: data, doesNotExist: false})
this.setState({calcData: data, doesNotExist: false})
}).catch(error => { }).catch(error => {
this.setState({calcData: null, doesNotExist: false})
if (error.name === 'DoesNotExist') { if (error.name === 'DoesNotExist') {
this.setState({doesNotExist: true}) setState({calcData: null, doesNotExist: true})
} else { } else {
this.props.raiseError(error) setState({calcData: null, doesNotExist: false})
raiseError(error)
} }
}) })
} }, [api, raiseError, uploadId, calcId, setState])
render() { const calcData = state.calcData || {uploadId: uploadId, calcId: calcId}
const { classes, ...calcProps } = this.props const loading = !state.calcData
const calcData = this.state.calcData || calcProps const quantityProps = {data: calcData, loading: loading}
const loading = !this.state.calcData
const { uploadId, calcId } = calcProps
const quantityProps = {data: calcData, loading: loading}
const authors = loading ? null : calcData.authors const authors = loading ? null : calcData.authors
const domain = calcData.domain && domains[calcData.domain] const domain = calcData.domain && domains[calcData.domain]
let entryHeader = 'Entry metadata' let entryHeader = 'Entry metadata'
if (domain) { if (domain) {
entryHeader = domain.entryTitle(calcData) entryHeader = domain.entryTitle(calcData)
} }
if (this.state.doesNotExist) { if (state.doesNotExist) {
return <Typography className={classes.error}> return <EntryPageContent className={classes.root} fixed>
This entry does not exist. <Typography className={classes.error}>
This entry does not exist.
</Typography> </Typography>
} </EntryPageContent>
}
return ( return (
<EntryPageContent className={classes.root} fixed> <EntryPageContent className={classes.root} fixed>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={7}> <Grid item xs={7}>
<Card> <Card>
<CardHeader <CardHeader
title={entryHeader} title={entryHeader}
action={<ApiDialogButton title="Repository JSON" data={calcData} />} action={<ApiDialogButton title="Repository JSON" data={calcData} />}
/> />
<CardContent classes={{root: classes.cardContent}}> <CardContent classes={{root: classes.cardContent}}>
{domain && <domain.EntryOverview data={calcData} loading={loading} />} {domain && <domain.EntryOverview data={calcData} loading={loading} />}
</CardContent> </CardContent>
<Divider /> <Divider />
<CardContent> <CardContent>
<Quantity column> <Quantity column>
<Quantity quantity='comment' placeholder='no comment' {...quantityProps} /> <Quantity quantity='comment' placeholder='no comment' {...quantityProps} />
<Quantity quantity='references' placeholder='no references' {...quantityProps}> <Quantity quantity='references' placeholder='no references' {...quantityProps}>
{calcData.references && {calcData.references &&
<div style={{display: 'inline-grid'}}> <div style={{display: 'inline-grid'}}>
{calcData.references.map(ref => <Typography key={ref} noWrap> {calcData.references.map(ref => <Typography key={ref} noWrap>
<a href={ref}>{ref}</a> <a href={ref}>{ref}</a>
</Typography>)} </Typography>)}
</div>} </div>}
</Quantity> </Quantity>
<Quantity quantity='authors' {...quantityProps}> <Quantity quantity='authors' {...quantityProps}>
<Typography> <Typography>
{(authors || []).map(author => author.name).join('; ')} {(authors || []).map(author => author.name).join('; ')}
</Typography> </Typography>
</Quantity>
<Quantity quantity='datasets' placeholder='no datasets' {...quantityProps}>
{calcData.datasets &&
<div>
{calcData.datasets.map(ds => (
<Typography key={ds.dataset_id}>
<Link component={RouterLink} to={`/dataset/id/${ds.dataset_id}`}>{ds.name}</Link>
{ds.doi ? <span>&nbsp; (<DOI doi={ds.doi}/>)</span> : ''}
</Typography>))}
</div>}
</Quantity>
</Quantity> </Quantity>
</CardContent> <Quantity quantity='datasets' placeholder='no datasets' {...quantityProps}>
</Card> {calcData.datasets &&
</Grid> <div>
{calcData.datasets.map(ds => (
<Typography key={ds.dataset_id}>
<Link component={RouterLink} to={`/dataset/id/${ds.dataset_id}`}>{ds.name}</Link>
{ds.doi ? <span>&nbsp; (<DOI doi={ds.doi}/>)</span> : ''}
</Typography>))}
</div>}
</Quantity>
</Quantity>
</CardContent>
</Card>
</Grid>
<Grid item xs={5}> <Grid item xs={5}>
<Card> <Card>
<CardHeader title="Ids / processing" /> <CardHeader title="Ids / processing" />
<CardContent classes={{root: classes.cardContent}}> <CardContent classes={{root: classes.cardContent}}>
<Quantity column style={{maxWidth: 350}}> <Quantity column style={{maxWidth: 350}}>
<Quantity quantity="calc_id" label={`${domain ? domain.entryLabel : 'entry'} id`} noWrap withClipboard {...quantityProps} /> <Quantity quantity="calc_id" label={`${domain ? domain.entryLabel : 'entry'} id`} noWrap withClipboard {...quantityProps} />
<Quantity quantity={entry => entry.encyclopedia.material.material_id} label='material id' loading={loading} noWrap {...quantityProps} withClipboard /> <Quantity quantity={entry => entry.encyclopedia.material.material_id} label='material id' loading={loading} noWrap {...quantityProps} withClipboard />
<Quantity quantity="raw_id" label='raw id' loading={loading} noWrap {...quantityProps} withClipboard /> <Quantity quantity="raw_id" label='raw id' loading={loading} noWrap {...quantityProps} withClipboard />
<Quantity quantity="external_id" label='external id' loading={loading} noWrap {...quantityProps} withClipboard /> <Quantity quantity="external_id" label='external id' loading={loading} noWrap {...quantityProps} withClipboard />
<Quantity quantity="mainfile" loading={loading} noWrap ellipsisFront {...quantityProps} withClipboard /> <Quantity quantity="mainfile" loading={loading} noWrap ellipsisFront {...quantityProps} withClipboard />
<Quantity quantity="upload_id" label='upload id' {...quantityProps} noWrap withClipboard /> <Quantity quantity="upload_id" label='upload id' {...quantityProps} noWrap withClipboard />
<Quantity quantity="upload_time" label='upload time' noWrap {...quantityProps} > <Quantity quantity="upload_time" label='upload time' noWrap {...quantityProps} >
<Typography noWrap> <Typography noWrap>
{new Date(calcData.upload_time).toLocaleString()} {new Date(calcData.upload_time).toLocaleString()}
</Typography> </Typography>
</Quantity>
<Quantity quantity="last_processing" label='last processing' loading={loading} placeholder="not processed" noWrap {...quantityProps}>
<Typography noWrap>
{new Date(calcData.last_processing).toLocaleString()}
</Typography>
</Quantity>
<Quantity quantity="last_processing" label='processing version' loading={loading} noWrap placeholder="not processed" {...quantityProps}>
<Typography noWrap>
{calcData.nomad_version}/{calcData.nomad_commit}
</Typography>
</Quantity>
</Quantity> </Quantity>
</CardContent> <Quantity quantity="last_processing" label='last processing' loading={loading} placeholder="not processed" noWrap {...quantityProps}>
</Card> <Typography noWrap>
</Grid> {new Date(calcData.last_processing).toLocaleString()}
</Typography>
</Quantity>
<Quantity quantity="last_processing" label='processing version' loading={loading} noWrap placeholder="not processed" {...quantityProps}>
<Typography noWrap>
{calcData.nomad_version}/{calcData.nomad_commit}
</Typography>
</Quantity>
</Quantity>
</CardContent>
</Card>
</Grid> </Grid>
</Grid>
{domain && <domain.EntryCards data={calcData} calcId={calcId} uploadId={uploadId} classes={{root: classes.entryCards}} />} {domain && <domain.EntryCards data={calcData} calcId={calcId} uploadId={uploadId} classes={{root: classes.entryCards}} />}
</EntryPageContent> </EntryPageContent>
) )
}
} }
export default compose(withApi(false, true), withStyles(RepoEntryView.styles))(RepoEntryView) RepoEntryView.propTypes = {
uploadId: PropTypes.string.isRequired,
calcId: PropTypes.string.isRequired
}
...@@ -24,7 +24,7 @@ def dev(): ...@@ -24,7 +24,7 @@ def dev():
pass pass
@dev.command(help='Runs tests and linting of the nomad source code. Useful before committing code.') @dev.command(help='Runs tests and linting of the nomad python source code. Useful before committing code.')
@click.option('--skip-tests', help='Do no tests, just do code checks.', is_flag=True) @click.option('--skip-tests', help='Do no tests, just do code checks.', is_flag=True)
@click.option('-x', '--exitfirst', help='Stop testing after first failed test case.', is_flag=True) @click.option('-x', '--exitfirst', help='Stop testing after first failed test case.', is_flag=True)
def qa(skip_tests: bool, exitfirst: bool): def qa(skip_tests: bool, exitfirst: bool):
...@@ -43,6 +43,15 @@ def qa(skip_tests: bool, exitfirst: bool): ...@@ -43,6 +43,15 @@ def qa(skip_tests: bool, exitfirst: bool):
sys.exit(ret_code) sys.exit(ret_code)
@dev.command(help='Runs tests and linting of the nomad gui source code. Useful before committing code.')
def gui_qa():
click.echo('Run gui code linting ...')
os.chdir(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../gui')))
ret_code = 0
ret_code += os.system('yarn run eslint \'src/**/*.js\'')
sys.exit(ret_code)
@dev.command(help='Generates a JSON with all metainfo.') @dev.command(help='Generates a JSON with all metainfo.')
def metainfo(): def metainfo():
import json import json
...@@ -144,7 +153,6 @@ def legacy_metainfo(path): ...@@ -144,7 +153,6 @@ def legacy_metainfo(path):
'qbox.nomadmetainfo.json', 'qbox.nomadmetainfo.json',
'quantum_espresso.nomadmetainfo.json', 'quantum_espresso.nomadmetainfo.json',
'siesta.nomadmetainfo.json', 'siesta.nomadmetainfo.json',
'skeleton.nomadmetainfo.json',
'turbomole.nomadmetainfo.json', 'turbomole.nomadmetainfo.json',
'vasp.nomadmetainfo.json', 'vasp.nomadmetainfo.json',
'wien2k.nomadmetainfo.json', 'wien2k.nomadmetainfo.json',
......
...@@ -289,8 +289,8 @@ max_upload_size = 32 * (1024 ** 3) ...@@ -289,8 +289,8 @@ max_upload_size = 32 * (1024 ** 3)
raw_file_strip_cutoff = 1000 raw_file_strip_cutoff = 1000
use_empty_parsers = False use_empty_parsers = False
reprocess_unmatched = True reprocess_unmatched = True