Commit 86a2cd73 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

First merge attempt with v0.8.0. Encyclopedia tests pass.

parents 1302ea85 47b112c3
Pipeline #72428 canceled with stages
in 3 minutes and 18 seconds
......@@ -8,10 +8,16 @@ module.exports = {
}
},
"globals": {
"fetch": false
"fetch": false,
"browser": true
},
"rules": {
"space-before-function-paren": ["error", "never"],
"camelcase": [0]
},
"settings": {
"react": {
"version": "detect"
}
}
}
\ No newline at end of file
{
"name": "nomad-fair-gui",
"version": "0.7.10",
"version": "0.8.0",
"commit": "nomad-gui-commit-placeholder",
"private": true,
"dependencies": {
......@@ -16,6 +16,7 @@
"file-saver": "^2.0.0",
"html-to-react": "^1.3.3",
"keycloak-js": "^6.0.0",
"lodash": "^4.17.15",
"marked": "^0.6.0",
"material-ui-chip-input": "^1.0.0-beta.14",
"material-ui-flat-pagination": "^3.2.0",
......
#!/bin/sh
find . -type f -exec sed -i "s_/fairdi/nomad/latest/gui_$1/gui_g" {} \;
nginx -g "daemon off;"
\ No newline at end of file
......@@ -5,15 +5,14 @@ import Markdown from './Markdown'
import { appBase, optimadeBase, apiBase, debug, consent } from '../config'
import { compose } from 'recompose'
import { withApi } from './api'
import { withDomain } from './domains'
import packageJson from '../../package.json'
import { domains } from './domains'
class About extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
api: PropTypes.object.isRequired,
info: PropTypes.object,
domain: PropTypes.object.isRequired,
raiseError: PropTypes.func.isRequired
}
......@@ -24,12 +23,33 @@ class About extends React.Component {
})
render() {
const { classes, domain, info } = this.props
const { classes, info } = this.props
return (
<div className={classes.root}>
<Markdown>{`
${domain.about}
# The NOMAD Repository and Archive
This web-page is the graphical user interface (GUI) for the NOMAD Repository and
Archive. It allows you to search, access, and download all NOMAD data in its
raw (Repository) and processed (Archive) form. You can upload and manage your own
raw computational material science data. Learn more about what data can be uploaded
and how to prepare your data on the [NOMAD Repository homepage](https://repository.nomad-coe.eu/).
You can access all published data without an account. If you want to provide
your own data, please login or register for an account.
In the future, this web-page will include more and more features of other NOMAD
components as an effort to consolidate the various web applications from the
NOMAD Repository, Archive, Metainfo, Encyclopedia, and Analytics Toolkit.
### This looks different, what about the old NOMAD interface?
We have migrated all data from the original NOMAD Repository to this new system.
However, not all of the data was successfully processed by the new and more powerful parsers.
We will continue to improve the parsers to raise the quality of archive data overtime.
For some entries, no archive data might be currently available and some metadata might
still be missing when you are exploring Nomad data using the new search and data
exploring capabilities (menu items on the left).
### Terms of use and licenses
${consent}
......@@ -41,6 +61,12 @@ class About extends React.Component {
about possible features, feel free to open an issue on our [issue tracking
system](https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR/issues).
### Material science data and domains
Originally NOMAD was build for DFT calculations and data from the respective
community code. By NOMAD supports multiple materials science domains:
${info && info.domains.map(domain => domains[domain.name]).map(domain => `- ${domain.name}: ${domain.about}`).join('\n')}
### ReST APIs
NOMAD services can also be accessed programmatically via ReST APIs.
There is the proprietary NOMAD API and an implementation of the
......@@ -87,7 +113,7 @@ class About extends React.Component {
### About this version
- version (API): \`${info ? info.version : 'loading'}/${info ? info.git.commit : 'loading'}\`
- version (GUI): \`${packageJson.version}/${packageJson.commit}\`
- domain: ${info ? info.domain.name : 'loading'}
- domains: ${info ? Object.keys(info.domains).map(domain => info.domains[domain].name).join(', ') : 'loading'}
- git: \`${info ? info.git.ref : 'loading'}; ${info ? info.git.version : 'loading'}\`
- last commit message: *${info ? info.git.log : 'loading'}*
- supported codes: ${info ? info.codes.join(', ') : 'loading'}
......@@ -99,4 +125,4 @@ class About extends React.Component {
}
}
export default compose(withApi(), withDomain, withStyles(About.styles))(About)
export default compose(withApi(), withStyles(About.styles))(About)
......@@ -91,10 +91,10 @@ class ApiDialogUnstyled extends React.Component {
<div className={classes.code}>
<div className={classes.json}>
<ReactJson
src={data}
enableClipboard={false}
collapsed={2}
displayObjectSize={false}
src={data}
enableClipboard={false}
collapsed={2}
displayObjectSize={false}
/>
</div>
</div>
......@@ -154,10 +154,10 @@ class ApiDialogButtonUnstyled extends React.Component {
return (
<div className={classes.root}>
{component ? component({onClick: this.handleShowDialog}) : <Tooltip title="Show API code">
<IconButton onClick={this.handleShowDialog}>
<CodeIcon />
</IconButton>
</Tooltip>
<IconButton onClick={this.handleShowDialog}>
<CodeIcon />
</IconButton>
</Tooltip>
}
<ApiDialog
{...dialogProps} open={showDialog}
......
......@@ -8,7 +8,7 @@ import { MuiThemeProvider, withStyles } from '@material-ui/core/styles'
import { LinearProgress, ListItemIcon, ListItemText, MenuList, MenuItem, Typography,
AppBar, Toolbar, Button, DialogContent, DialogTitle, DialogActions, Dialog, Tooltip,
Snackbar, SnackbarContent } from '@material-ui/core'
import { Switch, Route, Link, withRouter } from 'react-router-dom'
import { Route, Link, withRouter } from 'react-router-dom'
import BackupIcon from '@material-ui/icons/Backup'
import SearchIcon from '@material-ui/icons/Search'
import UserDataIcon from '@material-ui/icons/AccountCircle'
......@@ -23,7 +23,6 @@ import { help as entryHelp, default as EntryPage } from './entry/EntryPage'
import About from './About'
import LoginLogout from './LoginLogout'
import { guiBase, consent, nomadTheme } from '../config'
import { DomainProvider, withDomain } from './domains'
import {help as metainfoHelp, default as MetaInfoBrowser} from './metaInfoBrowser/MetaInfoBrowser'
import packageJson from '../../package.json'
import { Cookies, withCookies } from 'react-cookie'
......@@ -31,12 +30,12 @@ import Markdown from './Markdown'
import {help as uploadHelp, default as UploadPage} from './uploads/UploadPage'
import ResolvePID from './entry/ResolvePID'
import DatasetPage from './DatasetPage'
import { capitalize } from '../utils'
import { amber } from '@material-ui/core/colors'
import KeepState from './KeepState'
import {help as userdataHelp, default as UserdataPage} from './UserdataPage'
import ResolveDOI from './dataset/ResolveDOI'
import FAQ from './FAQ'
import EntryQuery from './entry/EntryQuery'
export const ScrollContext = React.createContext({scrollParentRef: null})
......@@ -57,7 +56,7 @@ function ReloadSnack() {
>
<SnackbarContent
style={{backgroundColor: amber[700]}}
message={<span>There is a new NOMAD version. Please press your browser's reload (or even shift+reload) button.</span>}
message={<span>There is a new NOMAD version. Please press your browser&apos;s reload (or even shift+reload) button.</span>}
/>
</Snackbar>
}
......@@ -68,8 +67,7 @@ class NavigationUnstyled extends React.Component {
children: PropTypes.any,
location: PropTypes.object.isRequired,
loading: PropTypes.number.isRequired,
raiseError: PropTypes.func.isRequired,
domain: PropTypes.object.isRequired
raiseError: PropTypes.func.isRequired
}
static styles = theme => ({
......@@ -164,7 +162,7 @@ class NavigationUnstyled extends React.Component {
'/uploads': 'Upload and Publish Data',
'/userdata': 'Manage Your Data',
'/metainfo': 'The NOMAD Meta Info',
'/entry': capitalize(this.props.domain.entryLabel),
'/entry': 'Entry',
'/dataset': 'Dataset'
}
......@@ -309,7 +307,7 @@ class NavigationUnstyled extends React.Component {
}
}
const Navigation = compose(withRouter, withErrors, withApi(false), withDomain, withStyles(NavigationUnstyled.styles))(NavigationUnstyled)
const Navigation = compose(withRouter, withErrors, withApi(false), withStyles(NavigationUnstyled.styles))(NavigationUnstyled)
class LicenseAgreementUnstyled extends React.Component {
static propTypes = {
......@@ -367,140 +365,77 @@ class LicenseAgreementUnstyled extends React.Component {
const LicenseAgreement = compose(withCookies, withStyles(LicenseAgreementUnstyled.styles))(LicenseAgreementUnstyled)
export default class App extends React.Component {
constructor(props) {
super(props)
this.renderChildren.bind(this)
}
class App extends React.PureComponent {
routes = {
'about': {
exact: true,
singleton: true,
path: '/',
render: props => <About {...props} />
component: About
},
'faq': {
exact: true,
singleton: true,
path: '/faq',
render: props => <FAQ {...props} />
component: FAQ
},
'search': {
exact: true,
singleton: true,
path: '/search',
render: props => <SearchPage {...props} />
component: SearchPage
},
'userdata': {
exact: true,
singleton: true,
path: '/userdata',
render: props => <UserdataPage {...props} />
component: UserdataPage
},
'entry': {
path: '/entry/id/:uploadId/:calcId',
key: (props) => `entry/id/${props.match.params.uploadId}/${props.match.params.uploadId}`,
render: props => {
const { match, ...rest } = props
if (match && match.params.uploadId && match.params.calcId) {
return (<EntryPage {...rest} uploadId={match.params.uploadId} calcId={match.params.calcId} />)
} else {
return ''
}
}
path: '/entry/id',
component: EntryPage
},
'entry_query': {
exact: true,
path: '/entry/query',
render: props => <EntryPage {...props} query />
},
'dataset': {
path: '/dataset/id/:datasetId',
key: (props) => `dataset/id/${props.match.params.datasetId}`,
render: props => {
const { match, ...rest } = props
if (match && match.params.datasetId) {
return (<DatasetPage {...rest} datasetId={match.params.datasetId} />)
} else {
return ''
}
}
component: EntryQuery
},
'entry_pid': {
path: '/entry/pid/:pid/:handle?',
key: (props) => `entry/pid/${props.match.params.pid}`,
render: props => {
const { match, ...rest } = props
if (match && match.params.pid) {
const {pid, handle} = match.params
return (<ResolvePID {...rest} pid={handle ? pid + '/' + handle : pid} />)
} else {
return ''
}
}
path: '/entry/pid',
component: ResolvePID
},
'dataset': {
path: '/dataset/id',
component: DatasetPage
},
'dataset_doi': {
path: '/dataset/doi/:doi*',
key: (props) => `dataset/doi/${props.match.params.doi}`,
render: props => {
const { match, ...rest } = props
if (match && match.params.doi) {
return (<ResolveDOI {...rest} doi={match.params.doi} />)
} else {
return ''
}
}
path: '/dataset/doi',
component: ResolveDOI
},
'uploads': {
exact: true,
singleton: true,
path: '/uploads',
render: props => <UploadPage {...props} />
component: UploadPage
},
'metainfo': {
exact: true,
path: '/metainfo',
singleton: true,
render: props => <MetaInfoBrowser {...props} />
},
'metainfoEntry': {
path: '/metainfo/:metainfo',
key: props => `metainfo/${props.match.params.metainfo}`,
render: props => <MetaInfoBrowser metainfo={props.match.params.metainfo} {...props} />
component: MetaInfoBrowser
}
}
renderChildren(routeKey, props) {
return (
<React.Fragment>
{Object.keys(this.routes).map(route => <KeepState key={route}
visible={routeKey === route}
render={(props) => this.routes[route].render(props)}
{...props} />)}
</React.Fragment>
)
}
render() {
return (
<MuiThemeProvider theme={nomadTheme}>
<ErrorSnacks>
<ApiProvider>
<DomainProvider>
<Navigation>
<Switch>
{Object.keys(this.routes).map(route => (
// eslint-disable-next-line react/jsx-key
<Route key={'nop'}
// eslint-disable-next-line react/no-children-prop
children={props => this.renderChildren(route, props)}
exact={this.routes[route].exact}
path={this.routes[route].path} />
))}
</Switch>
</Navigation>
</DomainProvider>
<Navigation>
{Object.keys(this.routes).map(routeKey => {
const route = this.routes[routeKey]
const { path, exact } = route
return <Route key={routeKey} exact={exact} path={path}
// eslint-disable-next-line react/no-children-prop
children={props => {
return <KeepState visible={props.match && true} render={route.component} {...props} />
}}
/>
})}
</Navigation>
</ApiProvider>
</ErrorSnacks>
<LicenseAgreement />
......@@ -508,3 +443,6 @@ export default class App extends React.Component {
)
}
}
const AppWithRouter = withRouter(App)
export default AppWithRouter
......@@ -16,7 +16,7 @@ import Tooltip from '@material-ui/core/Tooltip'
import ViewColumnIcon from '@material-ui/icons/ViewColumn'
import { Popover, List, ListItemText, ListItem, Collapse } from '@material-ui/core'
import { compose } from 'recompose'
import { withDomain } from './domains'
import _ from 'lodash'
class DataTableToolbarUnStyled extends React.Component {
static propTypes = {
......@@ -296,6 +296,12 @@ class DataTableUnStyled extends React.Component {
selectedColumns: null
}
componentDidUpdate(prevProps) {
if (prevProps.columns !== this.props.columns) {
this.setState({selectedColumns: this.props.selectedColumns})
}
}
handleRequestSort(event, property) {
const { orderBy, order, onOrderChanged } = this.props
const isDesc = orderBy === property && order === 'desc'
......@@ -489,7 +495,7 @@ class DataTableUnStyled extends React.Component {
key={key}
align={column.align || 'left'}
>
{column.render ? column.render(row) : row[key]}
{column.render ? column.render(row) : _.get(row, key)}
</TableCell>
)
})}
......@@ -513,4 +519,4 @@ class DataTableUnStyled extends React.Component {
}
}
export default compose(withDomain, withStyles(DataTableUnStyled.styles))(DataTableUnStyled)
export default compose(withStyles(DataTableUnStyled.styles))(DataTableUnStyled)
......@@ -8,8 +8,7 @@ import Search from './search/Search'
import SearchContext from './search/SearchContext'
import { Typography } from '@material-ui/core'
import { DatasetActions, DOI } from './search/DatasetList'
import { withRouter } from 'react-router'
import { withDomain } from './domains'
import { matchPath } from 'react-router'
export const help = `
This page allows you to **inspect** and **download** NOMAD datasets. It alsow allows you
......@@ -20,10 +19,10 @@ class DatasetPage extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
api: PropTypes.object.isRequired,
datasetId: PropTypes.string.isRequired,
raiseError: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
domain: PropTypes.object.isRequired
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
}
static styles = theme => ({
......@@ -50,8 +49,18 @@ class DatasetPage extends React.Component {
update: 0
}
datasetId() {
const { location, match } = this.props
const pidMatch = matchPath(location.pathname, {
path: `${match.path}/:datasetId`
})
let { datasetId } = pidMatch.params
return datasetId
}
update() {
const {datasetId, raiseError, api} = this.props
const { api, raiseError } = this.props
const datasetId = this.datasetId()
api.search({
owner: 'all',
dataset_id: datasetId,
......@@ -59,7 +68,7 @@ class DatasetPage extends React.Component {
per_page: 1
}).then(data => {
const entry = data.results[0]
const dataset = entry && entry.datasets.find(ds => ds.id + '' === datasetId)
const dataset = entry && entry.datasets.find(ds => ds.dataset_id + '' === datasetId)
if (!dataset) {
this.setState({dataset: {}, empty: true})
}
......@@ -77,7 +86,7 @@ class DatasetPage extends React.Component {
}
componentDidUpdate(prevProps) {
if (prevProps.api !== this.props.api || prevProps.datasetId !== this.props.datasetId) {
if (prevProps.location.pathname !== this.props.location.pathname || prevProps.api !== this.props.api) {
this.setState({dataset: {}, empty: false}, () => this.update())
}
}
......@@ -91,14 +100,15 @@ class DatasetPage extends React.Component {
}
render() {
const { classes, datasetId, domain } = this.props
const { classes } = this.props
const { dataset, update, empty } = this.state
const datasetId = this.datasetId()
return (
<div>
<div className={classes.header}>
<div className={classes.description}>
<Typography variant="h4">{dataset.name || (empty && 'Empty or non existing dataset') ||'loading ...'}</Typography>
<Typography variant="h4">{dataset.name || (empty && 'Empty or non existing dataset') || 'loading ...'}</Typography>
<Typography>
dataset{dataset.doi ? <span>, with DOI <DOI doi={dataset.doi} /></span> : ''}
</Typography>
......@@ -119,9 +129,6 @@ class DatasetPage extends React.Component {
>
<Search
resultTab="entries" tabs={['entries', 'groups', 'datasets']}
entryListProps={{
selectedColumns: [...domain.defaultSearchResultColumns, 'published', 'authors']
}}
/>
</SearchContext>
</div>
......@@ -129,4 +136,4 @@ class DatasetPage extends React.Component {
}
}
export default compose(withRouter, withDomain, withApi(false), withErrors, withStyles(DatasetPage.styles))(DatasetPage)
export default compose(withApi(false), withErrors, withStyles(DatasetPage.styles))(DatasetPage)
......@@ -39,7 +39,7 @@ class DownloadButton extends React.Component {
handleClick(event) {
event.stopPropagation()
this.setState({ anchorEl: event.currentTarget });
this.setState({ anchorEl: event.currentTarget })
}
async handleSelect(choice) {
......
......@@ -30,7 +30,7 @@ class FAQ extends React.Component {
### How can I be sure that my data will be cited properly?
Sharing means a change of culture. Making data open access is comparable to a
Sharing means a change of culture. Making data open access is comparable to a
publication where references to other work are common practice ever since.
Likewise, using someone's data requires proper citation. We recommend the uploader
to provide references to their data (publications, websites) and the users to
......@@ -43,7 +43,7 @@ class FAQ extends React.Component {
associated with your data might not change right away, but it will be updated
eventually.
### I'd like to install NOMAD on my local computers to be only used by my group
### I'd like to install NOMAD on my local computers to be only used by my group
Local NOMAD deployments are not actively supported at the moment. However, all
[NOMAD software](https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR) is
......
......@@ -3,8 +3,6 @@ import { withStyles, Button, IconButton, Dialog, DialogTitle, DialogContent, Dia
import Markdown from './Markdown'
import PropTypes from 'prop-types'
import HelpIcon from '@material-ui/icons/Help'
import { compose } from 'recompose'
import { withDomain } from './domains'
export const HelpContext = React.createContext()
......@@ -12,10 +10,9 @@ class HelpDialogUnstyled extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
title: PropTypes.string,
content: PropTypes.func.isRequired,
content: PropTypes.string.isRequired,
icon: PropTypes.node,
maxWidth: PropTypes.string,
domain: PropTypes.object.isRequired
maxWidth: PropTypes.string
}
static styles = theme => ({
......@@ -41,7 +38,7 @@ class HelpDialogUnstyled extends React.Component {
}
render() {
const {classes, title, content, icon, maxWidth, domain, ...rest} = this.props
const {classes, title, content, icon, maxWidth, ...rest} = this.props
return (
<div className={classes.root}>
<Tooltip title={title}>
......@@ -56,7 +53,7 @@ class HelpDialogUnstyled extends React.Component {
>
<DialogTitle>{title || 'Help'}</DialogTitle>
<DialogContent>
<Markdown>{content(domain)}</Markdown>
<Markdown>{content}</Markdown>
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleClose()} color="primary">
......@@ -69,4 +66,4 @@ class HelpDialogUnstyled extends React.Component {
}
}
export default compose(withDomain, withStyles(HelpDialogUnstyled.styles))(HelpDialogUnstyled)
export default withStyles(HelpDialogUnstyled.styles)(HelpDialogUnstyled)
import React from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'
/**
* This is a kinda-HOC that allows to keep a component alive while not being visible.
*/
export default class KeepState extends React.Component {