Commit e5535175 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added ems domain to GUI. Fixed ems domain in backend.

parent 48410f6c
Pipeline #46746 failed with stages
in 16 minutes and 36 seconds
Subproject commit 0d50b13f66f45891bc441a82af73bb425167ee71
Subproject commit 418ea7a2fae3c5726e970f52574916955ffe2fe4
Subproject commit 9056d2ac9586ee12f05fa76cdb8b09cc422c4af1
Subproject commit d39c14cfd23d98ff0431ce6fb0101d2f92339720
......@@ -260,16 +260,19 @@ export default class App extends React.Component {
routes = {
'about': {
exact: true,
singleton: true,
path: '/',
render: props => <About {...props} />
},
'search': {
exact: true,
singleton: true,
path: '/search',
render: props => <SearchPage {...props} />
},
'searchEntry': {
path: '/search/:uploadId/:calcId',
key: (props) => `searchEntry/${props.match.params.uploadId}/${props.match.params.uploadId}`,
render: props => {
const { match, ...rest } = props
if (match && match.params.uploadId && match.params.calcId) {
......@@ -281,11 +284,13 @@ export default class App extends React.Component {
},
'uploads': {
exact: true,
singleton: true,
path: '/uploads',
render: props => <Uploads {...props} />
},
'uploadedEntry': {
path: '/uploads/:uploadId/:calcId',
key: (props) => `uploadedEntry/${props.match.params.uploadId}/${props.match.params.uploadId}`,
render: props => {
const { match, ...rest } = props
if (match && match.params.uploadId && match.params.calcId) {
......@@ -302,11 +307,16 @@ export default class App extends React.Component {
return (
<div>
{Object.keys(this.routes).map(route => (
<div key={route} style={{display: routeKey === route ? 'block' : 'none'}}>
{this.routes[route].render(props)}
</div>
))}
{Object.keys(this.routes)
.filter(route => this.routes[route].singleton || route === routeKey)
.map(route => (
<div
key={route.key ? route.key(props) : route}
style={{display: routeKey === route ? 'block' : 'none'}}
>
{this.routes[route].render(props)}
</div>
))}
</div>
)
}
......
......@@ -10,25 +10,69 @@ class Quantity extends React.Component {
typography: PropTypes.string,
loading: PropTypes.bool,
placeholder: PropTypes.string,
noWrap: PropTypes.bool
noWrap: PropTypes.bool,
row: PropTypes.bool,
column: PropTypes.bool,
data: PropTypes.object,
quantity: PropTypes.string
}
static styles = theme => ({
root: {
margin: '8px 24px 0px 0'
root: {},
row: {
display: 'flex',
flexDirection: 'row',
'& > :not(:first-child)': {
marginLeft: theme.spacing.unit * 3
}
},
column: {
display: 'flex',
flexDirection: 'column',
'& > :not(:first-child)': {
marginTop: theme.spacing.unit * 1
}
}
})
render() {
const {classes, children, label, typography, loading, placeholder, noWrap} = this.props
const content = (!children || children.length === 0) ? null : children
const {classes, children, label, typography, loading, placeholder, noWrap, row, column, quantity, data} = this.props
let content = null
if (!loading) {
if (!(data && quantity && !data[quantity])) {
if (!children || children.length === 0) {
const value = data && quantity ? data[quantity] : null
if (value) {
content = <Typography noWrap={noWrap} variant={typography}>
{value}
</Typography>
} else {
content = <Typography noWrap={noWrap} variant={typography}>
<i>{placeholder || 'unavailable'}</i>
</Typography>
}
} else {
content = children
}
} else {
content = <Typography noWrap={noWrap} variant={typography}>
<i>{placeholder || 'unavailable'}</i>
</Typography>
}
}
return (
<div className={classes.root}>
<Typography variant="caption">{label}</Typography>
<Typography noWrap={noWrap} variant={typography || 'body1'}>{content || <i>{loading ? 'loading...' : placeholder || 'unavailable'}</i>}</Typography>
</div>
)
if (row || column) {
return <div className={row ? classes.row : classes.column}>{children}</div>
} else {
return (
<div className={classes.root}>
<Typography noWrap variant="caption">{label || quantity}</Typography>
{loading ? <Typography noWrap={noWrap} variant={typography}>
<i>loading ...</i>
</Typography> : content}
</div>
)
}
}
}
......
......@@ -15,13 +15,13 @@ class DFTEntryCards extends React.Component {
})
render() {
const { classes, data: {upload_id, calc_id, files} } = this.props
const { classes, data } = this.props
return (
<Card className={classes.root}>
<CardHeader title="Raw files" />
<CardContent classes={{root: classes.cardContent}}>
<RawFiles uploadId={upload_id} calcId={calc_id} files={files || []} />
<RawFiles data={data} />
</CardContent>
</Card>
)
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core'
import { Typography } from '@material-ui/core'
import Quantity from '../Quantity'
class DFTEntryOverview extends React.Component {
export default class DFTEntryOverview extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
loading: PropTypes.bool
}
static styles = theme => ({
quantityColumn: {
display: 'flex',
flexDirection: 'column'
},
quantityRow: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
marginBottom: theme.spacing.unit
}
})
render() {
const { classes, data, loading } = this.props
const { data } = this.props
return (
<div className={classes.quantityColumn}>
<div className={classes.quantityRow}>
<Quantity label='dft code' loading={loading}>
{data.code_name}
</Quantity>
<Quantity label='dft code version' loading={loading}>
{data.code_version}
</Quantity>
</div>
<div className={classes.quantityRow}>
<Quantity label='basis set' loading={loading}>
{data.basis_set}
</Quantity>
<Quantity label='xc functional' loading={loading}>
{data.xc_functional}
<Quantity column>
<Quantity row>
<Quantity quantity="code_name" label='dft code' noWrap {...this.props} />
<Quantity quantity="code_version" label='dft code version' noWrap {...this.props} />
</Quantity>
<Quantity row>
<Quantity quantity="basis_set" label='basis set' noWrap {...this.props} />
<Quantity quantity="xc_functional" label='xc functional' noWrap {...this.props} />
</Quantity>
<Quantity row>
<Quantity quantity="system_type" label='system type' noWrap {...this.props} />
<Quantity quantity="crystal_system" label='crystal system' noWrap {...this.props} />
<Quantity quantity='spacegroup_symbol' label="spacegroup" noWrap {...this.props}>
<Typography noWrap>
{data.spacegroup_symbol} ({data.spacegroup})
</Typography>
</Quantity>
</div>
<div className={classes.quantityRow}>
<Quantity label='system type' loading={loading}>
{data.system}
</Quantity>
<Quantity label='crystal system' loading={loading}>
{data.crystal_system}
</Quantity>
<Quantity label='spacegroup' loading={loading}>
{data.spacegroup_symbol} ({data.spacegroup})
</Quantity>
</div>
</div>
</Quantity>
</Quantity>
)
}
}
export default withStyles(DFTEntryOverview.styles)(DFTEntryOverview)
......@@ -3,6 +3,9 @@ import PropTypes from 'prop-types'
import DFTSearchAggregations from './dft/DFTSearchAggregations'
import DFTEntryOverview from './dft/DFTEntryOverview'
import DFTEntryCards from './dft/DFTEntryCards'
import EMSSearchAggregations from './ems/EMSSearchAggregations'
import EMSEntryOverview from './ems/EMSEntryOverview'
import EMSEntryCards from './ems/EMSEntryCards'
const DomainContext = React.createContext()
......@@ -15,7 +18,7 @@ export class DomainProvider extends React.Component {
}
dft = {
name: 'dft',
name: 'DFT',
/**
* A component that is used to render the search aggregations. The components needs
* to work with props: aggregations (the aggregation data from the api),
......@@ -91,6 +94,63 @@ export class DomainProvider extends React.Component {
EntryCards: DFTEntryCards
}
ems = {
name: 'EMS',
/**
* A component that is used to render the search aggregations. The components needs
* to work with props: aggregations (the aggregation data from the api),
* searchValues (currently selected search values), metric (the metric key to use),
* onChange (callback to propagate searchValue changes).
*/
SearchAggregations: EMSSearchAggregations,
/**
* Metrics are used to show values for aggregations. Each metric has a key (used
* for API calls), a label (used in the select form), and result string (to show
* the overall amount in search results).
*/
searchMetrics: {
code_runs: {
label: 'Entries',
renderResultString: count => (<span><b>{count}</b> entries</span>)
},
datasets: {
label: 'Datasets',
renderResultString: count => (<span> curated in <b>{count}</b> datasets</span>)
}
},
/**
* An dict where each object represents a column. Possible keys are label, render.
* Default render
*/
searchResultColumns: {
formula: {
label: 'Formula'
},
method: {
label: 'Method'
},
experiment_location: {
label: 'Location'
},
experiment_time: {
label: 'Date/Time',
render: time => new Date(time * 1000).toLocaleString()
}
},
/**
* 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),
* loading (a bool with api loading status).
*/
EntryOverview: EMSEntryOverview,
/**
* A component to render additional domain specific cards in the
* the entry view. Needs to work with props: data (the entry data from the API),
* loading (a bool with api loading status).
*/
EntryCards: EMSEntryCards
}
state = {
domain: this.dft
}
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, Card, CardHeader, CardContent } from '@material-ui/core'
import RawFiles from '../entry/RawFiles'
class EMSEntryCards extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
loading: PropTypes.bool
}
static styles = theme => ({
root: {}
})
render() {
const { classes, data } = this.props
return (
<Card className={classes.root}>
<CardHeader title="Raw files" />
<CardContent classes={{root: classes.cardContent}}>
<RawFiles data={data} />
</CardContent>
</Card>
)
}
}
export default withStyles(EMSEntryCards.styles)(EMSEntryCards)
import React from 'react'
import PropTypes from 'prop-types'
import Quantity from '../Quantity'
import { Typography } from '@material-ui/core'
export default class EMSEntryOverview extends React.Component {
static propTypes = {
data: PropTypes.object.isRequired,
loading: PropTypes.bool
}
render() {
const { data } = this.props
return (
<Quantity row>
<Quantity column>
<Quantity row>
<Quantity quantity='formula' label='sample formula' noWrap {...this.props} />
<Quantity quantity='chemical' label='sample chemical' noWrap {...this.props} />
</Quantity>
<Quantity quantity='method' label='experimental method' noWrap {...this.props} />
<Quantity quantity='experiment_location' label='experiment location' noWrap {...this.props} />
<Quantity label='experiment time' {...this.props}>
<Typography noWrap>
{new Date(data.experiment_time * 1000).toLocaleString()}
</Typography>
</Quantity>
<Quantity label='data' {...this.props}>
<Typography noWrap>
<a href={data.repository_url}>{data.repository_name}</a>
</Typography>
</Quantity>
</Quantity>
<Quantity label='preview' {...this.props}>
<img style={{maxWidth: '100%', height: 'auto'}} src={data.preview_url}></img>
</Quantity>
</Quantity>
)
}
}
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, Grid, Card, CardContent } from '@material-ui/core'
import PeriodicTable from '../search/PeriodicTable'
import QuantityHistogram from '../search/QuantityHistogram'
class EMSSearchAggregations extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
aggregations: PropTypes.object.isRequired,
metric: PropTypes.string.isRequired,
searchValues: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired
}
static styles = theme => ({
root: {},
quantity: {
marginTop: theme.spacing.unit * 2
},
quantityGrid: {
marginBottom: theme.spacing.unit * 2
}
})
handleAtomsChanged(atoms) {
const searchValues = {...this.props.searchValues}
searchValues.atoms = atoms
if (searchValues.atoms.length === 0) {
delete searchValues.atoms
}
this.props.onChange({searchValues: searchValues})
}
handleQuantityChanged(quantity, selection) {
const searchValues = {...this.props.searchValues}
if (selection) {
searchValues[quantity] = selection
} else {
delete searchValues[quantity]
}
this.props.onChange({searchValues: searchValues})
}
render() {
const { classes, aggregations, metric, searchValues } = this.props
const quantity = (key, title) => (<QuantityHistogram
classes={{root: classes.quantity}} title={title || key} width={300}
data={aggregations[key]} metric={metric}
value={searchValues[key]}
onChanged={(selection) => this.handleQuantityChanged(key, selection)}/>)
return (
<div className={classes.root}>
<Card>
<CardContent>
<PeriodicTable
aggregations={aggregations.atoms} metric={metric}
values={searchValues.atoms || []}
onChanged={(selection) => this.handleAtomsChanged(selection)}
/>
</CardContent>
</Card>
<Grid container spacing={24} className={classes.quantityGrid}>
<Grid item xs={6}>
{quantity('method', 'Method')}
</Grid>
<Grid item xs={6}>
{quantity('experiment_location', 'Location')}
</Grid>
</Grid>
</div>
)
}
}
export default withStyles(EMSSearchAggregations.styles)(EMSSearchAggregations)
......@@ -10,7 +10,7 @@ class Calc extends React.Component {
root: {
},
content: {
padding: 0,
padding: `0 ${theme.spacing.unit * 3}px`,
maxWidth: 1024,
margin: 'auto'
}
......
......@@ -9,11 +9,10 @@ import Download from './Download'
class RawFiles extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
uploadId: PropTypes.string.isRequired,
calcId: PropTypes.string.isRequired,
files: PropTypes.arrayOf(PropTypes.string).isRequired,
data: PropTypes.object.isRequired,
api: PropTypes.object.isRequired,
user: PropTypes.object
user: PropTypes.object,
loading: PropTypes.number.isRequired
}
static styles = theme => ({
......@@ -43,10 +42,11 @@ class RawFiles extends React.Component {
}
render() {
const {classes, files, uploadId, calcId} = this.props
const {classes, data: {files, upload_id, calc_id}, loading} = this.props
const availableFiles = files || []
const {selectedFiles} = this.state
const someSelected = selectedFiles.length > 0
const allSelected = files.length === selectedFiles.length && someSelected
const allSelected = availableFiles.length === selectedFiles.length && someSelected
return (
<div className={classes.root}>
......@@ -56,27 +56,28 @@ class RawFiles extends React.Component {
control={
<Checkbox value="select_all" checked={allSelected}
indeterminate={!allSelected && someSelected}
onChange={() => this.setState({selectedFiles: allSelected ? [] : files.slice()})}
onChange={() => this.setState({selectedFiles: allSelected ? [] : availableFiles.slice()})}
/>
}
/>
<FormLabel className={classes.formLabel}>
{selectedFiles.length}/{files.length} files selected
{selectedFiles.length}/{availableFiles.length} files selected
</FormLabel>
<Download component={IconButton} disabled={selectedFiles.length === 0}
tooltip="download selected files"
url={(selectedFiles.length === 1) ? `raw/${uploadId}/${selectedFiles[0]}` : `raw/${uploadId}?files=${encodeURIComponent(selectedFiles.join(','))}`}
fileName={selectedFiles.length === 1 ? this.label(selectedFiles[0]) : `${calcId}.zip`}
url={(selectedFiles.length === 1) ? `raw/${upload_id}/${selectedFiles[0]}` : `raw/${calc_id}?files=${encodeURIComponent(selectedFiles.join(','))}`}
fileName={selectedFiles.length === 1 ? this.label(selectedFiles[0]) : `${calc_id}.zip`}
>
<DownloadIcon />
</Download>
</FormGroup>
<Divider />
<FormGroup row>
{files.map((file, index) => (
{availableFiles.map((file, index) => (
<FormControlLabel key={index} label={this.label(file)}
control={
<Checkbox
disabled={loading > 0}
checked={selectedFiles.indexOf(file) !== -1}
onChange={() => this.onSelectFile(file)} value={file}
/>
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, Divider, Card, CardContent, Grid, CardHeader, Fab } from '@material-ui/core'
import { withStyles, Divider, Card, CardContent, Grid, CardHeader, Fab, Typography } from '@material-ui/core'
import { withApi } from '../api'
import { compose } from 'recompose'
import Download from './Download'
......@@ -18,19 +18,6 @@ class RepoEntryView extends React.Component {
content: {
marginTop: theme.spacing.unit * 3
},
quantityContainer: {
display: 'flex'
},
quantityColumn: {
display: 'flex',
flexDirection: 'column'
},
quantityRow: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
marginBottom: theme.spacing.unit
},
downloadFab: {
zIndex: 1,