Commit b4ebc0f7 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Added basis set setting handling, re-added prototype information, restructured...

Added basis set setting handling, re-added prototype information, restructured section_encyclopedia.
parents a335148c 8444bcd6
......@@ -31,6 +31,8 @@ Omitted versions are plain bugfix releases with only minor changes and fixes.
### v0.7.5
- AFLOWLIB prototypes (archive)
- primitive label search
- improved search performance based on excluded fields
- improved logs
- minor bugfixes
......
Subproject commit 5bfbdf09cbd0e9a2934467bdeca569296952ee06
Subproject commit b50054a10b28efddb82554051d797b44f2b1067e
Subproject commit 76e5c29e3ed8d1d1167c03b138020498831ef896
Subproject commit 15d0110cbeda05aaea05e4d30ba3aeb0874dafef
Subproject commit fe15759f080e8176d88af91447949243608b0d7e
Subproject commit b39569c5fa69254c90f91ec430d28a0941efbe95
......@@ -3,6 +3,8 @@ 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()
......@@ -10,9 +12,10 @@ class HelpDialogUnstyled extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
title: PropTypes.string,
content: PropTypes.string.isRequired,
content: PropTypes.func.isRequired,
icon: PropTypes.node,
maxWidth: PropTypes.string
maxWidth: PropTypes.string,
domain: PropTypes.object.isRequired
}
static styles = theme => ({
......@@ -38,7 +41,7 @@ class HelpDialogUnstyled extends React.Component {
}
render() {
const {classes, title, content, icon, maxWidth, ...rest} = this.props
const {classes, title, content, icon, maxWidth, domain, ...rest} = this.props
return (
<div className={classes.root}>
<Tooltip title={title}>
......@@ -53,7 +56,7 @@ class HelpDialogUnstyled extends React.Component {
>
<DialogTitle>{title || 'Help'}</DialogTitle>
<DialogContent>
<Markdown>{content}</Markdown>
<Markdown>{content(domain)}</Markdown>
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleClose()} color="primary">
......@@ -66,4 +69,4 @@ class HelpDialogUnstyled extends React.Component {
}
}
export default withStyles(HelpDialogUnstyled.styles)(HelpDialogUnstyled)
export default compose(withDomain, withStyles(HelpDialogUnstyled.styles))(HelpDialogUnstyled)
......@@ -3,7 +3,7 @@ import { withApi } from './api'
import Search from './search/Search'
import SearchContext from './search/SearchContext'
export const help = `
export const help = domain => `
This page allows you to **inspect** and **manage** you own data. It is similar to the
*search page*, but it will only show data that was uploaded by you or is shared with you.
......
......@@ -207,6 +207,8 @@ class Api {
this.onStartLoading = () => null
this.onFinishLoading = () => null
this.statistics = {}
this._swaggerClient = Swagger(`${apiBase}/swagger.json`)
this.keycloak = keycloak
......@@ -384,9 +386,34 @@ class Api {
async search(search) {
this.onStartLoading()
return this.swagger()
.then(client => client.apis.repo.search(search))
.then(client => client.apis.repo.search({
exclude: ['atoms', 'only_atoms', 'files', 'quantities', 'optimade', 'labels', 'geometries'],
...search}))
.catch(handleApiError)
.then(response => response.body)
.then(response => {
// fill absent statistics values with values from prior searches
// this helps to keep consistent values, e.g. in the metadata search view
if (response.statistics) {
const empty = {}
Object.keys(response.statistics.total.all).forEach(metric => empty[metric] = 0)
Object.keys(response.statistics)
.filter(key => !['total', 'authors', 'atoms'].includes(key))
.forEach(key => {
if (!this.statistics[key]) {
this.statistics[key] = new Set()
}
const values = this.statistics[key]
Object.keys(response.statistics[key]).forEach(value => values.add(value))
values.forEach(value => {
if (!response.statistics[key][value]) {
response.statistics[key][value] = empty
}
})
})
}
return response
})
.finally(this.onFinishLoading)
}
......
......@@ -102,10 +102,10 @@ class DomainProviderBase extends React.Component {
defaultSearchMetric: 'code_runs',
additionalSearchKeys: {
raw_id: {},
external_id: {},
upload_id: {},
calc_id: {},
paths: {},
external_id: {},
pid: {},
mainfile: {},
calc_hash: {},
......@@ -113,8 +113,9 @@ class DomainProviderBase extends React.Component {
optimade: {},
quantities: {},
spacegroup: {},
specegroup_symbol: {},
labels: {}
spacegroup_symbol: {},
labels: {},
upload_name: {}
},
/**
* An dict where each object represents a column. Possible keys are label, render.
......
......@@ -10,7 +10,7 @@ import qs from 'qs'
import KeepState from '../KeepState'
import { guiBase } from '../../config'
export const help=`
export const help = domain => `
The *raw files* tab, will show you all files that belong to the entry and offers a download
on individual, or all files. The files can be selected and downloaded. You can also
view the contents of some files directly here on this page.
......
......@@ -177,8 +177,8 @@ class RawFiles extends React.Component {
}
filterPotcar(file) {
if (file.toLowerCase().endsWith('potcar')) {
return this.props.data.uploader.user_id === this.props.user.sub
if (file.includes('POTCAR') && !file.endsWith('.stripped')) {
return this.props.user && this.props.data.uploader.user_id === this.props.user.sub
} else {
return true
}
......
......@@ -132,16 +132,16 @@ class RepoEntryView extends React.Component {
<Quantity column style={{maxWidth: 350}}>
<Quantity quantity="calc_id" label={`${domain.entryLabel} id`} noWrap withClipboard {...quantityProps} />
<Quantity quantity="pid" label='PID' loading={loading} placeholder="not yet assigned" 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="mainfile" loading={loading} noWrap ellipsisFront {...quantityProps} withClipboard />
<Quantity quantity="calc_hash" label={`${domain.entryLabel} hash`} loading={loading} noWrap {...quantityProps} />
<Quantity quantity="upload_id" label='upload id' {...quantityProps} noWrap withClipboard />
<Quantity quantity="upload_time" label='upload time' noWrap {...quantityProps} >
<Typography noWrap>
{new Date(calcData.upload_time * 1000).toLocaleString()}
</Typography>
</Quantity>
<Quantity quantity='mainfile' loading={loading} noWrap ellipsisFront {...quantityProps} withClipboard />
<Quantity quantity="calc_hash" label={`${domain.entryLabel} hash`} loading={loading} noWrap {...quantityProps} />
<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="last_processing" label='last processing' loading={loading} placeholder="not processed" noWrap {...quantityProps}>
<Typography noWrap>
{new Date(calcData.last_processing * 1000).toLocaleString()}
......
......@@ -9,7 +9,7 @@ import { FormControl, withStyles, Select, Input, MenuItem, ListItemText, InputLa
import { compose } from 'recompose'
import { schema } from '../MetaInfoRepository'
export const help = `
export const help = domain => `
The NOMAD *metainfo* defines all quantities used to represent archive data in
NOMAD. You could say it is the archive *schema*. You can browse this schema and
all its definitions here.
......
......@@ -264,18 +264,10 @@ export class EntryListUnstyled extends React.Component {
<Quantity column >
{/* <Quantity quantity="pid" label='PID' placeholder="not yet assigned" noWrap data={row} withClipboard /> */}
<Quantity quantity="calc_id" label={`${domain.entryLabel} id`} noWrap withClipboard data={row} />
<Quantity quantity="upload_id" label='upload id' data={row} noWrap withClipboard />
<Quantity quantity="raw_id" label={`raw id`} noWrap withClipboard data={row} />
<Quantity quantity="external_id" label={`external id`} noWrap withClipboard data={row} />
<Quantity quantity='mainfile' noWrap ellipsisFront data={row} withClipboard />
<Quantity quantity="upload_time" label='upload time' noWrap data={row} >
<Typography noWrap>
{new Date(row.upload_time * 1000).toLocaleString()}
</Typography>
</Quantity>
<Quantity quantity="last_processing" label='processing version' noWrap placeholder="not processed" data={row}>
<Typography noWrap>
{row.nomad_version}/{row.nomad_commit}
</Typography>
</Quantity>
<Quantity quantity="upload_id" label='upload id' data={row} noWrap withClipboard />
</Quantity>
</div>
</div>
......
......@@ -7,8 +7,9 @@ import { withApi } from '../api'
import Search from './Search'
import SearchContext from './SearchContext'
import qs from 'qs'
import { withDomain } from '../domains'
export const help = `
export const help = domain => `
This page allows you to **search** in NOMAD's data. The upper part of this page
gives you various options to enter and configure your search. The lower part
shows all data that fulfills your search criteria.
......@@ -29,6 +30,11 @@ The visual representations show metrics for all data that fit your criteria.
You can display *entries* (i.e. code runs), *unique entries*, and *datasets*.
Other more specific metrics might be available.
Some quantities have no autocompletion for their values. You can still search for them,
if you know exactly what you are looking for. To search for a particular entry by its id
for example, type \`calc_id=<the_id>\` and press entry (or select the respective item from the menu).
The usable *hidden* quantities are: ${Object.keys(domain.additionalSearchKeys).map(key => `\`${key}\``).join(', ')}.
The results tabs gives you a quick overview of all entries and datasets that fit your search.
You can click entries to see more details, download data, see the archive, etc. The *entries*
tab displays individual entries (i.e. code runs), the *grouped entries* tab will group
......@@ -53,7 +59,8 @@ class SearchPage extends React.Component {
user: PropTypes.object,
location: PropTypes.object,
raiseError: PropTypes.func.isRequired,
update: PropTypes.number
update: PropTypes.number,
domain: PropTypes.object
}
static styles = theme => ({
......@@ -93,4 +100,4 @@ class SearchPage extends React.Component {
}
}
export default compose(withApi(false), withErrors, withStyles(SearchPage.styles))(SearchPage)
export default compose(withDomain, withApi(false), withErrors, withStyles(SearchPage.styles))(SearchPage)
......@@ -17,7 +17,7 @@ import { CopyToClipboard } from 'react-copy-to-clipboard'
import { guiBase } from '../../config'
import qs from 'qs'
export const help = `
export const help = domain => `
NOMAD allows you to upload data. After upload, NOMAD will process your data: it will
identify the main output files of [supported codes](https://www.nomad-coe.eu/the-project/nomad-repository/nomad-repository-howtoupload)
and then it will parse these files. The result will be a list of entries (one per each identified mainfile).
......@@ -308,7 +308,7 @@ class UploadPage extends React.Component {
</Tooltip>
{/* <button>Copy to clipboard with button</button> */}
</CopyToClipboard>
<HelpDialog icon={<MoreIcon/>} maxWidth="md" title="Alternative shell commands" content={`
<HelpDialog icon={<MoreIcon/>} maxWidth="md" title="Alternative shell commands" content={domain => `
As an experienced shell and *curl* user, you can modify the commands to
your liking.
......
......@@ -147,6 +147,7 @@ class ArchiveDownloadResource(Resource):
search_request = search.SearchRequest()
apply_search_parameters(search_request, args)
search_request.include('calc_id', 'upload_id', 'mainfile')
calcs = search_request.execute_scan(
order_by='upload_id',
......@@ -273,6 +274,7 @@ class ArchiveQueryResource(Resource):
search_request = search.SearchRequest()
apply_search_parameters(search_request, args)
search_request.include('calc_id', 'upload_id', 'mainfile')
try:
if scroll:
......
......@@ -421,6 +421,7 @@ class RawFileQueryResource(Resource):
search_request = search.SearchRequest()
apply_search_parameters(search_request, _raw_file_from_query_parser.parse_args())
search_request.include('calc_id', 'upload_id', 'mainfile')
def path(entry):
return '%s/%s' % (entry['upload_id'], entry['mainfile'])
......
......@@ -84,6 +84,8 @@ _search_request_parser.add_argument(
'Possible values are %s.' % ', '.join(datamodel.Domain.instance.metrics_names)))
_search_request_parser.add_argument(
'statistics', type=bool, help=('Return statistics.'))
_search_request_parser.add_argument(
'exclude', type=str, action='split', help='Excludes the given keys in the returned data.')
for group_name in search.groups:
_search_request_parser.add_argument(
group_name, type=bool, help=('Return %s group data.' % group_name))
......@@ -150,8 +152,9 @@ class RepoCalcsResource(Resource):
"""
try:
parsed_args = _search_request_parser.parse_args()
args = {
key: value for key, value in _search_request_parser.parse_args().items()
key: value for key, value in parsed_args.items()
if value is not None}
scroll = args.get('scroll', False)
......@@ -202,6 +205,11 @@ class RepoCalcsResource(Resource):
elif len(metrics) > 0:
search_request.totals(metrics_to_use=metrics)
if 'exclude' in parsed_args:
excludes = parsed_args['exclude']
if excludes is not None:
search_request.exclude(*excludes)
try:
if scroll:
results = search_request.execute_scrolled(scroll_id=scroll_id, size=per_page)
......@@ -297,7 +305,7 @@ _repo_edit_model = api.model('RepoEdit', {
def edit(parsed_query: Dict[str, Any], mongo_update: Dict[str, Any] = None, re_index=True) -> List[str]:
# get all calculations that have to change
with utils.timer(common.logger, 'edit query executed'):
search_request = search.SearchRequest()
search_request = search.SearchRequest().include('calc_id', 'upload_id')
apply_search_parameters(search_request, parsed_query)
upload_ids = set()
calc_ids = []
......@@ -689,7 +697,7 @@ class RepoPidResource(Resource):
except ValueError:
abort(400, 'Wrong PID format')
search_request = search.SearchRequest()
search_request = search.SearchRequest().include('upload_id', 'calc_id')
if g.user is not None:
search_request.owner('all', user_id=g.user.user_id)
......
......@@ -315,7 +315,7 @@ class UploadListResource(Resource):
upload.process_upload()
logger.info('initiated processing')
if bool(request.args.get('token', False)):
if bool(request.args.get('token', False)) and request.headers.get('Accept', '') != 'application/json':
raise DisableMarshalling(
'''
Thanks for uploading your data to nomad.
......
......@@ -65,7 +65,7 @@ class CalculationList(Resource):
except Exception:
abort(400, message='bad parameter types') # TODO Specific json API error handling
search_request = base_search_request()
search_request = base_search_request().include('calc_id')
if filter is not None:
try:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment