Commit 39220d69 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Merge branch 'v0.10.1' into 'master'

Merge for release of v0.10.1

See merge request !304
parents b4dcae6b 63b8306f
Pipeline #97853 passed with stage
in 24 seconds
......@@ -74,7 +74,8 @@ python tests:
stage: test
image: $TEST_IMAGE
services:
- rabbitmq
- name: rabbitmq:3.7.17
alias: rabbitmq
- name: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
alias: elastic
# fix issue with running elastic in gitlab ci runner:
......@@ -241,6 +242,6 @@ pypi package:
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN gitlab-registry.mpcdf.mpg.de
- docker pull $TEST_IMAGE
- docker run --rm $TEST_IMAGE bash -c "python -m twine upload -u $CI_TWINE_USER -p $CI_TWINE_PASSWORD dist/nomad-lab-*.tar.gz"
except:
- /^dev-.*$/
when: manual
only:
- tags
......@@ -76,8 +76,8 @@
branch = master
[submodule "dependencies/parsers/phonopy"]
path = dependencies/parsers/phonopy
url = https://gitlab.mpcdf.mpg.de/nomad-lab/parser-phonopy.git
branch = nomad-fair
url = https://github.com/nomad-coe/nomad-parser-phonopy.git
branch = master
[submodule "dependencies/parsers/gpaw"]
path = dependencies/parsers/gpaw
url = https://github.com/nomad-coe/nomad-parser-gpaw.git
......@@ -110,10 +110,6 @@
path = dependencies/parsers/turbomole
url = https://github.com/nomad-coe/nomad-parser-turbomole.git
branch = master
[submodule "dependencies/parsers/skeleton"]
path = dependencies/parsers/skeleton
url = https://gitlab.mpcdf.mpg.de/nomad-lab/parser-skeleton.git
branch = nomad-fair
[submodule "dependencies/parsers/mpes"]
path = dependencies/parsers/mpes
url = https://github.com/nomad-coe/nomad-parser-mpes.git
......
Subproject commit 6566fba137ccfd725b1b902febc59f680c89b561
Subproject commit c43f397c9d65b6c3ec86c2b51b51b473576b6253
Subproject commit b11c18bb9e0781b80b7c5c49fbaf5ec6d97831ed
Subproject commit 8cfae714ee2a026775d42edfad03f5d83964af27
Subproject commit bf2182de246d0b63866e8a61b94392afb8b78073
Subproject commit a3578e122fb1d775dc759d1d2a51cd5da5a0f0ba
Subproject commit 6ff8642f195b4eab1f6284d51f455348763602b6
Subproject commit 2f17711b7de36f57e6844fdb8420b750d75369c8
Subproject commit ce55910cabb529b7c0751017c5f10e803e2594a3
Subproject commit 82322068adfdd96579fd2bde341923c132926f6d
Subproject commit 94dc1aa46ac7c5c0f1cbb6f1a00798331e0b5200
Subproject commit 9258f9521823698fcdfbdf85d5665d7694fafe5e
Subproject commit b9b5f1b074dd71a1a09368e214a91820c54ce94b
const fs = require('fs')
const packageJson = require('./package.json')
const appCommit = packageJson.commit
const jsonData = {
commit: appCommit
}
var jsonContent = JSON.stringify(jsonData)
fs.writeFile('./public/meta.json', jsonContent, 'utf8', function(err) {
if (err) {
console.log('An error occured while writing JSON Object to meta.json')
return console.log(err)
}
console.log('meta.json file has been saved with latest version number')
})
......@@ -7,6 +7,8 @@
"../dependencies/materia"
],
"dependencies": {
"@fontsource/material-icons": "^4.2.1",
"@fontsource/titillium-web": "^4.2.2",
"@material-ui/core": "^4.0.0",
"@material-ui/icons": "^4.0.0",
"@material-ui/lab": "^4.0.0-alpha.49",
......@@ -62,8 +64,6 @@
"use-query-params": "^0.6.0"
},
"scripts": {
"generate-build-version": "node generateBuildVersion",
"prebuild": "npm run generate-build-version",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jest-environment-jsdom-sixteen --watchAll=false",
......
......@@ -22,6 +22,7 @@
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
......@@ -32,13 +33,6 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<!-- fonts needed by material-ui -->
<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Mono:400,500"> -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Titillium+Web:400,600">
<!-- icon fonts for the meta-info browser -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<title>NOMAD</title>
......
......@@ -335,8 +335,8 @@ export default function About() {
If you encounter any difficulties, please write to
[support@nomad-lab.eu](mailto:support@nomad-lab.eu). If you think
that this web-page is not working as expected, or if you want to start a discussion
about possible features, feel free to open an issue on our [issue tracking
system](https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR/issues).
about possible features, feel free to open an issue on our
[github project](https://github.com/nomad-coe/nomad/issues).
### Developer Documentation
The [in-depth documentation](${appBase}/docs/index.html)
......
......@@ -26,7 +26,7 @@ import Keycloak from 'keycloak-js'
import { KeycloakProvider } from 'react-keycloak'
import { MuiThemeProvider } from '@material-ui/core/styles'
import { ApiProvider } from './api'
import { ErrorSnacks } from './errors'
import { ErrorSnacks, ErrorBoundary } from './errors'
import Navigation from './nav/Navigation'
export const matomo = matomoEnabled ? PiwikReactRouter({
......@@ -51,9 +51,11 @@ export default function App() {
<QueryParamProvider ReactRouterRoute={Route}>
<MuiThemeProvider theme={nomadTheme}>
<ErrorSnacks>
<ApiProvider>
<Navigation />
</ApiProvider>
<ErrorBoundary>
<ApiProvider>
<Navigation />
</ApiProvider>
</ErrorBoundary>
</ErrorSnacks>
</MuiThemeProvider>
</QueryParamProvider>
......
......@@ -17,9 +17,10 @@
*/
import React from 'react'
import PropTypes from 'prop-types'
import { SnackbarContent, IconButton, Snackbar, withStyles } from '@material-ui/core'
import ErrorIcon from '@material-ui/icons/Error'
import { SnackbarContent, IconButton, Snackbar, withStyles, Typography, Box } from '@material-ui/core'
import CloseIcon from '@material-ui/icons/Close'
import { serviceWorkerRegistrationRef } from '..'
import Markdown from './Markdown'
export class VersionMismatch extends Error {
constructor(msg) {
......@@ -42,18 +43,6 @@ class ErrorSnacksUnstyled extends React.Component {
root: {},
errorSnack: {
backgroundColor: theme.palette.error.dark
},
icon: {
fontSize: 20,
opacity: 0.9,
marginRight: theme.spacing(1)
},
message: {
display: 'flex',
alignItems: 'center'
},
errors: {
marginLeft: theme.spacing(1)
}
})
......@@ -80,8 +69,8 @@ class ErrorSnacksUnstyled extends React.Component {
} else if (error.message) {
errorStr = `Unexpected error: "${error.message}". Please try again and let us know, if this error keeps happening.`
}
} else if (error instanceof String) {
errorStr = `Unexpected error: "${error}". Please try again and let us know, if this error keeps happening.`
} else if (typeof error === 'string' || error instanceof String) {
errorStr = `${error} Please try to reload and let us know, if this error keeps happening.`
}
if (this.state.errors.indexOf(errorStr) === -1) {
......@@ -90,7 +79,9 @@ class ErrorSnacksUnstyled extends React.Component {
}
onClose() {
this.setState({errors: []})
if (this.state.errors.length > 0) {
this.setState({errors: this.state.errors.slice(1)})
}
}
render() {
......@@ -101,23 +92,15 @@ class ErrorSnacksUnstyled extends React.Component {
<Snackbar
anchorOrigin={{vertical: 'bottom', horizontal: 'left'}}
open={this.state.errors.length > 0}
// autoHideDuration={6000}
onClose={this.handleClose}
>
<SnackbarContent
className={classes.errorSnack}
message={
<span id="client-snackbar" className={classes.message}>
<ErrorIcon className={classes.icon} />
<div className={classes.errors}>
{this.state.errors.map((error, index) => (
<p key={index}>{'' + error}</p>
))}
</div>
</span>
<span style={{color: 'white'}}>{'' + this.state.errors[0]}</span>
}
action={[
<IconButton key={0} color="inherit" onClick={this.onClose.bind(this)}>
<IconButton key={0} size="small" color="inherit" onClick={this.onClose.bind(this)}>
<CloseIcon />
</IconButton>
]}
......@@ -141,3 +124,54 @@ export function withErrors(Component) {
return WithErrorComponent
}
export class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static propTypes = {
children: PropTypes.any,
onError: PropTypes.func
}
static getDerivedStateFromError(_error) {
// Update state so the next render will show the fallback UI.
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
console.log('cought error in boundary', error, errorInfo, serviceWorkerRegistrationRef)
// check for a newer version of the app
if (serviceWorkerRegistrationRef.current) {
console.log('try service worker update')
serviceWorkerRegistrationRef.current.update()
}
if (this.context) {
this.context.raiseError('There has been a Javascript error.')
}
}
render() {
if (this.state.hasError) {
return <Box margin={2}>
<Typography color="error">
Something went wrong in this part of the app (Javascript error). Please try to
reload and let us know, if this error keeps happening.
</Typography>
<Markdown>
{`
Please, write to [support@nomad-lab.eu](mailto:support@nomad-lab.eu), or
open an issue on our [github project](https://github.com/nomad-coe/nomad/issues).
`}
</Markdown>
</Box>
}
return this.props.children
}
}
ErrorBoundary.contextType = errorContext
......@@ -18,28 +18,55 @@
import React, { useEffect, useRef, useState } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import { Snackbar, SnackbarContent, IconButton, Link as MuiLink } from '@material-ui/core'
import { Snackbar, SnackbarContent, IconButton, Link as MuiLink, Button } from '@material-ui/core'
import UnderstoodIcon from '@material-ui/icons/Check'
import ReloadIcon from '@material-ui/icons/Replay'
import { amber } from '@material-ui/core/colors'
import AppBar from './AppBar'
import { guiBase, version } from '../../config'
import packageJson from '../../../package.json'
import { version } from '../../config'
import Routes from './Routes'
import { withApi } from '../api'
import { serviceWorkerUpdateHandlerRef } from '../../index'
import { ErrorBoundary } from '../errors'
export const ScrollContext = React.createContext({scrollParentRef: null})
function ReloadSnack() {
const waitingServiceWorker = useRef(null)
const [reload, setReload] = useState(false)
useEffect(() => {
serviceWorkerUpdateHandlerRef.current = registration => {
waitingServiceWorker.current = registration.waiting
setReload(true)
}
}, [setReload, waitingServiceWorker])
return <Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
open
open={reload}
>
<SnackbarContent
style={{backgroundColor: amber[700]}}
message={<span>There is a new NOMAD version. Please press your browser&apos;s reload (or even shift+reload) button.</span>}
message={<span>There is a new NOMAD version. Please reload the app.</span>}
action={[
<Button
key={0} color="inherit" startIcon={<ReloadIcon/>}
onClick={() => {
if (waitingServiceWorker.current) {
waitingServiceWorker.current.onstatechange = () => {
if (waitingServiceWorker.current.state === 'activated') {
window.location.reload()
}
}
waitingServiceWorker.current.postMessage({type: 'SKIP_WAITING'})
}
}}
>
reload
</Button>
]}
/>
</Snackbar>
}
......@@ -73,12 +100,12 @@ function BetaSnack() {
<SnackbarContent
className={classes.snack}
message={<span style={{color: 'white'}}>
You are using a {version.isBeta ? 'beta' : 'test'} version of NOMAD ({version.label}). {
You are using a {version.isBeta ? 'beta' : 'test'} version of NOMAD ({version.label}). {
version.usesBetaData ? 'This version is not using the official data. Everything you upload here, might get lost.' : ''
} Click <MuiLink style={{color: 'white'}} href={version.officialUrl}>here for the official NOMAD version</MuiLink>.
</span>}
action={[
<IconButton key={0} color="inherit" onClick={() => setUnderstood(true)}>
<IconButton size="small" key={0} color="inherit" onClick={() => setUnderstood(true)}>
<UnderstoodIcon />
</IconButton>
]}
......@@ -109,41 +136,21 @@ const useStyles = makeStyles(theme => ({
function Navigation() {
const classes = useStyles()
const [showReloadSnack, setShowReloadSnack] = useState(false)
const scrollParentRef = useRef(null)
useEffect(() => {
fetch(`${guiBase}/meta.json`, {
method: 'GET',
cache: 'no-cache',
headers: {
'Pragma': 'no-cache',
'Cache-Control': 'no-cache, no-store'
}
}).then((response) => response.json())
.then((meta) => {
if (meta.commit !== packageJson.commit) {
console.log('GUI API version mismatch')
setShowReloadSnack(true)
}
})
.catch(() => {
console.log('Could not validate version, continue...')
})
}, [setShowReloadSnack])
return (
<div className={classes.root}>
<div className={classes.appFrame}>
{ showReloadSnack ? <ReloadSnack/> : ''}
<BetaSnack />
<AppBar />
<main className={classes.content} ref={scrollParentRef}>
<ScrollContext.Provider value={{scrollParentRef: scrollParentRef}}>
<Routes/>
</ScrollContext.Provider>
</main>
<ReloadSnack/>
<ErrorBoundary>
<BetaSnack />
<AppBar />
<main className={classes.content} ref={scrollParentRef}>
<ScrollContext.Provider value={{scrollParentRef: scrollParentRef}}>
<Routes/>
</ScrollContext.Provider>
</main>
</ErrorBoundary>
</div>
</div>
)
......
......@@ -31,6 +31,7 @@ import FAQ from '../FAQ'
import SearchPage, {help as searchHelp} from '../search/SearchPage'
import UploadPage, {help as uploadHelp} from '../uploads/UploadPage'
import UserdataPage, {help as userdataHelp} from '../UserdataPage'
import { ErrorBoundary } from '../errors'
export const routes = {
'faq': {
......@@ -161,10 +162,12 @@ export default function Routes() {
const route = allRoutes[routeKey]
const { path, exact } = route
const children = childProps => childProps.match && <route.component {...childProps} />
return <Route key={routeKey} exact={exact} path={path}
// eslint-disable-next-line react/no-children-prop
children={children}
/>
return <ErrorBoundary key={routeKey}>
<Route exact={exact} path={path}
// eslint-disable-next-line react/no-children-prop
children={children}
/>
</ErrorBoundary>
})}
</React.Fragment>
}
......@@ -21,12 +21,35 @@ import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './components/App'
import '@fontsource/titillium-web/400.css'
import '@fontsource/titillium-web/600.css'
import '@fontsource/material-icons'
import * as serviceWorker from './serviceWorker'
export const serviceWorkerUpdateHandlerRef = {
current: null
}
export const serviceWorkerRegistrationRef = {
current: null
}
ReactDOM.render(<App />, document.getElementById('root'))
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister()
serviceWorker.register({
onSuccess: registration => {
serviceWorkerRegistrationRef.current = registration
setInterval(() => {
registration.update()
console.debug('Checked for update...')
}, (1000 * 60) * 30)
},
onUpdate: registration => {
if (serviceWorkerUpdateHandlerRef.current) {
serviceWorkerUpdateHandlerRef.current(registration)
}
}
})
......@@ -1165,6 +1165,16 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
 
"@fontsource/material-icons@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@fontsource/material-icons/-/material-icons-4.2.1.tgz#b673a9a37a984721ff8edc9a9e8566c42081587d"
integrity sha512-u5JBOQ7lOWlBtkTTvhR2zM01sJIqLnt+tR6lEBFzYOU/J/eWZmI+C6QC3/HH7gWO2YQYRmxhI/OuyvzV0EiywQ==
"@fontsource/titillium-web@^4.2.2":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@fontsource/titillium-web/-/titillium-web-4.2.2.tgz#7a3482e9421240674162de0706587126c0c35109"
integrity sha512-OQeXcX4UuuyttAq1qe49gz6Afxsl/3UOS1dqip3Sw0NYZFv4N4NdysX7Z0qJMtXm1yvXhVjjJ79iyTYr1qy33A==
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
......@@ -10046,6 +10056,7 @@ node-releases@^1.1.52, node-releases@^1.1.69:
"nomad-fair-gui@file:.":
version "0.10.1"
dependencies:
"@fontsource/titillium-web" "^4.2.2"
"@material-ui/core" "^4.0.0"
"@material-ui/icons" "^4.0.0"
"@material-ui/lab" "^4.0.0-alpha.49"
......@@ -10066,7 +10077,7 @@ node-releases@^1.1.52, node-releases@^1.1.69:
material-ui-chip-input "^1.0.0-beta.14"
material-ui-flat-pagination "^4.0.0"
mathjs "^7.1.0"
nomad-fair-gui "file:../../../.cache/yarn/v6/npm-nomad-fair-gui-0.10.1-f8e02023-90bb-473d-8c51-03dddb2be5c9-1616096663867/node_modules/nomad-fair-gui"
nomad-fair-gui "file:../../../../Library/Caches/Yarn/v6/npm-nomad-fair-gui-0.10.1-caae6be0-51b7-44c5-86db-e97037c5c024-1617035416277/node_modules/nomad-fair-gui"
object-hash "^2.0.3"
pace "^0.0.4"
pace-js "^1.0.2"
......
......@@ -102,16 +102,30 @@ class MaterialSearch():
self._extra = extra
def s(self):
# Wrap in bool query
if self._q is None:
query = Q(
"bool",
filter=self._filters,
)
query = Q("bool", filter=self._filters)
else:
query = self._q
if not isinstance(query, Bool):
query = Q("bool", filter=[query])
# Split the query into material specific part and calculation specific
# part. For now the queries are "separable", but this may not be the
# case in the future...
m_query = Q("bool")
c_query = Q("bool")
for q in query.must:
c_query.must.append(q) if isinstance(q, Nested) else m_query.must.append(q)
for q in query.filter:
c_query.filter.append(q) if isinstance(q, Nested) else m_query.filter.append(q)
for q in query.must_not:
c_query.must_not.append(q) if isinstance(q, Nested) else m_query.must_not.append(q)
for q in query.should:
c_query.should.append(q) if isinstance(q, Nested) else m_query.should.append(q)
# If restricted search is enabled, the order of nested/boolean queries
# will be reversed depth-first.
# will be reversed depth-first in the calculation specific part.
if self.restricted:
def restrict(query):
if isinstance(query, Bool):
......@@ -140,11 +154,18 @@ class MaterialSearch():
return outer_q
else:
return query
query = restrict(query)
c_query = restrict(c_query)
# Wrap the query in a boolean query if it is not already one.
if not isinstance(query, Bool):
query = Q("bool", filter=[query])
# Wrap in a boolean query if it is not already one.
if not isinstance(c_query, Bool):
c_query = Q("bool", filter=[c_query])
# Merge calculation and material specific parts
query = Q("bool")
query.filter = c_query.filter + m_query.filter
query.must = c_query.must + m_query.must
query.must_not = c_query.must_not + m_query.must_not
query.should = c_query.should + m_query.should
# Add authentication filters on top of the query. This will make sure
# that materials with only private calculations are excluded and that
......@@ -191,6 +212,8 @@ class MaterialSearch():
should_to_or(query)
s = self._s.query(query)
# import json
# print(json.dumps(s.to_dict(), indent=2))
extra = self._extra
s = s.extra(**extra)
return s
......@@ -263,7 +286,7 @@ class MaterialSearch():
quantities: List[MQuantity] = [
# Material level quantities
MQuantity("elements", es_field="species", elastic_mapping_type=Text, has_only_quantity=MQuantity(name="species.keyword")),
MQuantity("formula", es_field="formula_reduced", elastic_mapping_type=Keyword, converter=lambda x: "".join(query_from_formula(x).split())),
MQuantity("formula", es_field="species_and_counts", elastic_mapping_type=Text, has_only_quantity=MQuantity(name="species_and_counts.keyword")),
MQuantity("material_id", es_field="material_id", elastic_mapping_type=Keyword),
MQuantity("material_type", es_field="material_type", elastic_mapping_type=Keyword),
MQuantity("material_name", es_field="material_name", elastic_mapping_type=Keyword),
......@@ -1470,22 +1493,24 @@ class EncCalculationResource(Resource):
suggestions_map = {
"code_name": "dft.code_name",
"basis_set": "dft.basis_set",