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

Misceleneous fixes.

parent a9c94891
Pipeline #65646 passed with stages
in 28 minutes and 57 seconds
......@@ -23,7 +23,8 @@ class FAQ extends React.Component {
<Markdown>{`
# Frequently Asked Questions (FAQ)
These are often repeated questions that cover the basic NOMAD use-cases.
These are often repeated questions that cover the basic NOMAD use-cases. If you have
further questions, please write use: [${email}](mailto:${email}).
## Upload data, datasets, embargo, and DOIs
......@@ -129,9 +130,6 @@ class FAQ extends React.Component {
If you are familiar with the input and output format of other relevant codes,
write us an Email ([${email}](mailto:${email})) and we will figure our if and how
to support this code in the future.
## I have a new question
Please write use: [${email}](mailto:${email}).
`}</Markdown>
</div>
)
......
......@@ -50,7 +50,7 @@ On the dataset table, you can also click the DOI button to assign a DOI to a dat
This DOI can be used in publications to link to your dataset. If people resovle the
DOI, they will be redirected to a NOMAD view that shows the dataset and allows its download.
Once you assigned a DOI to a dataset, not entries can be removed or added to the dataset.
Once you assigned a DOI to a dataset, no entries can be removed or added to the dataset.
`
class UserdataPage extends React.Component {
......
......@@ -47,7 +47,7 @@ class DomainProviderBase extends React.Component {
still be missing when you are exploring Nomad data using the new search and data
exploring capabilities (menu items on the left).
`,
entryLabel: 'code run',
entryLabel: 'entry',
searchPlaceholder: 'enter atoms, codes, functionals, or other quantity values',
/**
* A component that is used to render the search aggregations. The components needs
......
......@@ -85,7 +85,7 @@ class ArchiveLogView extends React.Component {
<Download
classes={{root: classes.downloadFab}} tooltip="download logfile"
component={Fab} className={classes.downloadFab} color="secondary" size="medium"
component={Fab} className={classes.downloadFab} size="medium"
url={`archive/logs/${uploadId}/${calcId}`} fileName={`${calcId}.log`}
>
<DownloadIcon />
......
......@@ -89,7 +89,7 @@ class RawFiles extends React.Component {
}
update() {
const { uploadId, calcId } = this.props
const { uploadId, calcId, raiseError } = this.props
// this might accidentally happen, when the user logs out and the ids aren't
// necessarily available anymore, but the component is still mounted
if (!uploadId || !calcId) {
......@@ -98,13 +98,16 @@ class RawFiles extends React.Component {
this.props.api.getRawFileListFromCalc(uploadId, calcId).then(data => {
const files = data.contents.map(file => `${data.directory}/${file.name}`)
if (files.length > 500) {
raiseError('There are more than 500 files in this entry. We can only show the first 500.')
}
this.setState({files: files})
}).catch(error => {
this.setState({files: null})
if (error.name === 'DoesNotExist') {
this.setState({doesNotExist: true})
} else {
this.props.raiseError(error)
raiseError(error)
}
})
}
......
......@@ -130,6 +130,7 @@ class RepoEntryView extends React.Component {
<CardHeader title="Ids / processing" />
<CardContent classes={{root: classes.cardContent}}>
<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="upload_id" label='upload id' {...quantityProps} noWrap withClipboard />
<Quantity quantity="upload_time" label='upload time' noWrap {...quantityProps} >
......@@ -137,7 +138,6 @@ class RepoEntryView extends React.Component {
{new Date(calcData.upload_time * 1000).toLocaleString()}
</Typography>
</Quantity>
<Quantity quantity="calc_id" label={`${domain.entryLabel} id`} noWrap withClipboard {...quantityProps} />
<Quantity quantity='mainfile' loading={loading} noWrap {...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 />
......
......@@ -45,7 +45,7 @@ const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 300
width: 300, maxHeight: '90vh'
}
}
}
......
......@@ -27,6 +27,7 @@ export class EntryListUnstyled extends React.Component {
columns: PropTypes.object,
title: PropTypes.string,
actions: PropTypes.element,
showEntryActions: PropTypes.func,
selectedColumns: PropTypes.arrayOf(PropTypes.string)
}
......@@ -224,8 +225,8 @@ export class EntryListUnstyled extends React.Component {
<div className={classes.entryDetailsRow} style={{maxWidth: '33%', paddingRight: 0}}>
<Quantity column >
{/* <Quantity quantity="pid" label='PID' placeholder="not yet assigned" noWrap data={row} withClipboard /> */}
<Quantity quantity="upload_id" label='upload id' data={row} noWrap 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='mainfile' noWrap data={row} withClipboard />
<Quantity quantity="upload_time" label='upload time' noWrap data={row} >
<Typography noWrap>
......@@ -255,11 +256,15 @@ export class EntryListUnstyled extends React.Component {
}
renderEntryActions(row, selected) {
return <Tooltip title="View entry page">
<IconButton style={selected ? {color: 'white'} : null} onClick={event => this.handleViewEntryPage(event, row)}>
<DetailsIcon />
</IconButton>
</Tooltip>
if (!this.props.showEntryActions || this.props.showEntryActions(row)) {
return <Tooltip title="View entry page">
<IconButton style={selected ? {color: 'white'} : null} onClick={event => this.handleViewEntryPage(event, row)}>
<DetailsIcon />
</IconButton>
</Tooltip>
} else {
return ''
}
}
render() {
......@@ -277,7 +282,7 @@ export class EntryListUnstyled extends React.Component {
const defaultSelectedColumns = this.props.selectedColumns || [
...domain.defaultSearchResultColumns,
'datasets', 'authors']
'authors']
const pagination = <TablePagination
count={totalNumber}
......
......@@ -47,8 +47,8 @@ class SearchContext extends React.Component {
response: SearchContext.emptyResponse,
request: {
statistics: true,
order_by: 'formula',
order: 1,
order_by: 'upload_time',
order: -1,
page: 1,
per_page: 10
},
......
......@@ -29,8 +29,21 @@ The visual representations show metrics for all data that fit your criteria.
You can display *entries* (e.g. code runs), *unique entries*, and *datasets*.
Other more specific metrics might be available.
The results table 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 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 (e.g. code runs), the *grouped entries* tab will group
entries with similar metadata (e.g. it will group entries for the same material from the
same user). The *dataset* tab, shows entry curated by user created datasets. You can
click on datasets for a search page that will only display entries from the respective
dataset.
The table columns can be configured. The *entries* tab also supports sorting. Selected
entries (or all entries) can be downloaded. The download will contain all user provided
raw calculation input and output files.
You can click entries to see more details about them. The details button will navigate
you to an entries page. These entry pages will show more metadata, raw files, the
entry's archive, and processing logs.
`
class SearchPage extends React.Component {
......
......@@ -496,6 +496,7 @@ class Upload extends React.Component {
data={data}
onChange={this.handleChange}
actions={actions}
showEntryActions={entry => entry.processed}
{...this.state.params}
/>
}
......
......@@ -24,7 +24,7 @@ and then it will parse these files. The result will be a list of entries (one pe
Each entry is associated with metadata. This is data that NOMAD acquired from your files and that
describe your calculations (e.g. chemical formula, used code, system type and symmetry, etc.).
Furthermore, you can provide your own metadata (comments, references, co-authors, etc.).
First uploaded data is only visible to you. Before others can actually see and download
At first, uploaded data is only visible to you. Before others can actually see and download
your data, you need to publish your upload.
#### Prepare and upload files
......@@ -65,19 +65,19 @@ If you press publish, a dialog will appear that allows you to set an
*embargo* or publish your data as *Open Access* right away. The *embargo* allows you to share
data with selected users, create a DOI for your data, and later publish the data.
The *embargo* might last up to 36 month before data becomes public automatically.
During an *embargo* the data (and datasets created from this data) are only visible to you.
During an *embargo* the data (and datasets created from this data) are only visible to you
and users you *share with* the data.
#### Processing errors
We distinguish between uploads that fail processing completely and uploads that contain
entries that could not be processed. The former might be caused by issues during the
upload, bad file formats, etc. The latter (for more common) case means that not all of the provided
code output files could not be parsed by our parsers for various reasons.
The processing logs of the failed entries might provide some insight.
code output files could be parsed by our parsers. The processing logs of the failed entries might provide some insight.
We do not allow the publishing of uploads that fail processing completely. Frankly, in most
You can not publish uploads that failed processing completely. Frankly, in most
cases there won't be any data to publish anyways. In the case of failed processing of
some entries, the data can still be published. You will be able to share it and create
some entries however, the data can still be published. You will be able to share it and create
DOIs for it, etc. The only shortcomings will be missing metadata (labeled *not processed*
or *unavailable*) and missing archive data. We continuously improve our parsers and
the now missing information might become available in the future automatically.
......@@ -88,8 +88,8 @@ You can edit additional *user metadata*. This data is assigned to individual ent
you can select and edit many entries at once. Edit buttons for user metadata are available
in many views on this web-page. For example, you can edit user metadata when you click on
an upload to open its details, and press the edit button there. User metadata can also
be changed after publishing data. The documentation on the [user data page](${guiBase}/userdata) contains more
information.
be changed after publishing data. The documentation on the [user data page](${guiBase}/userdata)
contains more information.
`
class Uploads extends React.Component {
......
......@@ -23,6 +23,7 @@ from flask import request, g
from elasticsearch_dsl import Q
from elasticsearch.exceptions import NotFoundError
import elasticsearch.helpers
from datetime import datetime
from nomad import search, utils, datamodel, processing as proc, infrastructure
from nomad.app.utils import rfc3339DateTime, RFC3339DateTime, with_logger
......@@ -502,7 +503,7 @@ class EditRepoCalcsResource(Resource):
if not verify:
dataset = Dataset(
dataset_id=utils.create_uuid(), user_id=g.user.user_id,
name=action_value)
name=action_value, created=datetime.utcnow())
dataset.m_x('me').create()
mongo_value = dataset.dataset_id
......@@ -568,6 +569,7 @@ class EditRepoCalcsResource(Resource):
return json_data, 400
# perform the change
mongo_update['metadata__last_edit'] = datetime.utcnow()
upload_ids = edit(parsed_query, logger, mongo_update, True)
# lift embargo
......
......@@ -106,6 +106,7 @@ class CalcWithMetadata(Mapping):
self.references: List[str] = []
self.datasets: List[str] = []
self.external_id: str = None
self.last_edit: datetime.datetime = None
# parser related general (not domain specific) metadata
self.parser_name = None
......
......@@ -93,6 +93,7 @@ class Dataset(metainfo.MSection):
full URL, e.g. "10.17172/nomad/2019.10.29-1".
pid: The original NOMAD CoE Repository dataset PID. Old DOIs still reference
datasets based on this id. Is not used for new datasets.
created: The date when the dataset was first created.
"""
dataset_id = metainfo.Quantity(
type=str,
......@@ -109,6 +110,9 @@ class Dataset(metainfo.MSection):
pid = metainfo.Quantity(
type=str,
a_me=dict(index=True))
created = metainfo.Quantity(
type=metainfo.Datetime,
a_me=dict(index=True))
class UserMetadata(metainfo.MSection):
......
......@@ -66,11 +66,13 @@ class MESection():
return self.to_metainfo(me_obj)
def to_metainfo(self, me_obj):
dct = me_obj.to_mongo().to_dict()
del(dct['_id'])
dct[self.id_quantity] = getattr(me_obj, self.id_quantity)
section = self.section_cls.m_from_dict(dct) # pylint: disable=no-member
section = self.section_cls()
section.m_x('me').me_obj = me_obj
for quantity in self.section_cls.m_def.all_quantities.keys():
value = getattr(me_obj, quantity)
if value is not None:
setattr(section, quantity, value)
return section
......
......@@ -1096,11 +1096,13 @@ class TestEditRepo():
assert not has_failure == success
assert has_message == message
def mongo(self, *args, **kwargs):
def mongo(self, *args, edited: bool = True, **kwargs):
for calc_id in args:
calc = Calc.objects(calc_id=str(calc_id)).first()
assert calc is not None
metadata = calc.metadata
if edited:
assert metadata.get('last_edit') is not None
for key, value in kwargs.items():
if metadata.get(key) != value:
return False
......@@ -1151,30 +1153,30 @@ class TestEditRepo():
self.assert_edit(rv, quantity='comment', success=True, message=False)
assert self.mongo(1, 2, 3, comment='test_edit_all')
assert self.elastic(1, 2, 3, comment='test_edit_all')
assert not self.mongo(4, comment='test_edit_all')
assert not self.elastic(4, comment='test_edit_all')
assert not self.mongo(4, comment='test_edit_all', edited=False)
assert not self.elastic(4, comment='test_edit_all', edited=False)
def test_edit_multi(self):
rv = self.perform_edit(comment='test_edit_multi', query=dict(upload_id='upload_1,upload_2'))
self.assert_edit(rv, quantity='comment', success=True, message=False)
assert self.mongo(1, 2, 3, comment='test_edit_multi')
assert self.elastic(1, 2, 3, comment='test_edit_multi')
assert not self.mongo(4, comment='test_edit_multi')
assert not self.elastic(4, comment='test_edit_multi')
assert not self.mongo(4, comment='test_edit_multi', edited=False)
assert not self.elastic(4, comment='test_edit_multi', edited=False)
def test_edit_some(self):
rv = self.perform_edit(comment='test_edit_some', query=dict(upload_id='upload_1'))
self.assert_edit(rv, quantity='comment', success=True, message=False)
assert self.mongo(1, comment='test_edit_some')
assert self.elastic(1, comment='test_edit_some')
assert not self.mongo(2, 3, 4, comment='test_edit_some')
assert not self.elastic(2, 3, 4, comment='test_edit_some')
assert not self.mongo(2, 3, 4, comment='test_edit_some', edited=False)
assert not self.elastic(2, 3, 4, comment='test_edit_some', edited=False)
def test_edit_verify(self):
rv = self.perform_edit(
comment='test_edit_verify', verify=True, query=dict(upload_id='upload_1'))
self.assert_edit(rv, quantity='comment', success=True, message=False)
assert not self.mongo(1, comment='test_edit_verify')
assert not self.mongo(1, comment='test_edit_verify', edited=False)
def test_edit_empty_list(self, other_test_user):
rv = self.perform_edit(coauthors=[other_test_user.user_id], query=dict(upload_id='upload_1'))
......
Markdown is supported
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