From e724ba3f1782edbbc10d17eeba64a8a5473fa1d4 Mon Sep 17 00:00:00 2001
From: Markus Scheidgen <markus.scheidgen@gmail.com>
Date: Tue, 14 May 2019 11:58:39 +0200
Subject: [PATCH] Miscellenous fixes for beta testing.

---
 dependencies/parsers/mpes               |  2 +-
 gui/src/components/About.js             | 37 ++++----------
 gui/src/components/domains.js           | 64 +++++++++++++++++++++++++
 gui/src/components/search/SearchPage.js |  5 +-
 gui/src/components/uploads/Upload.js    |  7 ++-
 nomad/api/repo.py                       | 13 +++--
 nomad/api/upload.py                     | 59 +++++++++++++++++++++--
 nomad/parsing/__init__.py               |  2 +-
 nomad/parsing/parser.py                 |  2 +-
 nomad/processing/data.py                |  9 +++-
 10 files changed, 157 insertions(+), 43 deletions(-)

diff --git a/dependencies/parsers/mpes b/dependencies/parsers/mpes
index e772c6d954..26e4b0b365 160000
--- a/dependencies/parsers/mpes
+++ b/dependencies/parsers/mpes
@@ -1 +1 @@
-Subproject commit e772c6d954009d7b3419200ffd370f55fd62c1a4
+Subproject commit 26e4b0b365501f84eaceb12f9d79c04fb1faca38
diff --git a/gui/src/components/About.js b/gui/src/components/About.js
index cfdf0ce6e2..4ab023b90e 100644
--- a/gui/src/components/About.js
+++ b/gui/src/components/About.js
@@ -5,11 +5,13 @@ import Markdown from './Markdown'
 import { kibanaBase, apiBase } from '../config'
 import { compose } from 'recompose'
 import { withApi } from './api'
+import { withDomain } from './domains'
 
 class About extends React.Component {
   static propTypes = {
     classes: PropTypes.object.isRequired,
     api: PropTypes.object.isRequired,
+    domain: PropTypes.object.isRequired,
     raiseError: PropTypes.func.isRequired
   }
 
@@ -32,36 +34,13 @@ class About extends React.Component {
   }
 
   render() {
-    const { classes } = this.props
+    const { classes, domain } = this.props
     const { info } = this.state
 
     return (
       <div className={classes.root}>
         <Markdown>{`
-          ## The nomad**@FAIRDI** *beta* test
-
-          **!Please read this, before you explore this new part of Nomad!**
-
-          ### About nomad@FAIRDI
-
-          After the conclusion of the origin [NOMAD-coe](http://nomad-coe.eu) project,
-          the newly founded NGO *FAIR Data Infrastructures* (FAIRDI) provides an
-          umbrella to continue operation and further development of the original Nomad
-          software. Therefore, we use the name *nomad@FAIRDI* to refer to the new
-          consolidated nomad data infrastructure.
-
-          As a first step, we refined the Nomad upload and data processing.
-
-          This is a prototype, a concept, for a continuation of the
-          [NOMAD-coe](http://nomad-coe.eu) project. It is an attempt to redesign
-          the nomad software and infrastructure with the following goals in mind:
-
-          * more immediate and near-time use-modes (*staging area*, *code integration*, *on-site data*)
-          * 3rd parties run instances of the nomad on their servers (*mirrors*, *oasis*, *industry* usage)
-          * mirrors(partially) synchronize data with the central nomad instance (*data federation*)
-          * the nomad architecture/infrastructure is used for related *domains* (e.g. experimental material science) or even more unrelated domains
-          * nomad is integrated with existing *Open Data* initiatives and databases (*FAIRDI*, *EUDAT*, *optimade*)
-          * we benefit from nomad being *Open Source* (public git, outside participation)
+          ${domain.about}
 
           ### Developer Documentation
           You find in depth developer documentation [here](${apiBase}/docs/index.html).
@@ -92,9 +71,9 @@ class About extends React.Component {
           ### Test user
           During development this GUI might not be connected to the actual nomad
           repository. Therefore, you cannot create a user or login with an existing
-          user. You might use our test users \`sheldon.cooper@nomad-fairdi.tests.de\`
-          or \`leonard.hofstadter@nomad-fairdi.tests.de\` both
-          with password \`password\`.
+          user. You might use the test user \`leonard.hofstadter@nomad-fairdi.tests.de\`
+          with password \`password\`. The user \`sheldon.cooper@nomad-fairdi.tests.de\` is
+          used for data that has no provenance with the original Nomad CoE database.
 
           ### About this version
           - version: \`${info ? info.version : 'loading'}/${info ? info.release : 'loading'}\`
@@ -109,4 +88,4 @@ class About extends React.Component {
   }
 }
 
-export default compose(withApi(), withStyles(About.styles))(About)
+export default compose(withApi(), withDomain, withStyles(About.styles))(About)
diff --git a/gui/src/components/domains.js b/gui/src/components/domains.js
index 8b03cb686b..6990fdd5ed 100644
--- a/gui/src/components/domains.js
+++ b/gui/src/components/domains.js
@@ -23,6 +23,55 @@ class DomainProviderBase extends React.Component {
   domains = {
     DFT: {
       name: 'DFT',
+      about: `
+        ## The nomad**@FAIRDI** *beta* test
+
+        ### About nomad@FAIRDI
+
+        After the conclusion of the original [NOMAD-coe](http://nomad-coe.eu) project,
+        the newly founded NGO *FAIR Data Infrastructures* (FAIRDI) provides an
+        umbrella to continue operation and further development of the Nomad
+        material science data sharing platform.
+
+        The immediate goal is to to consolidate and stabilize the nomad infrastructure, and
+        as a first step, we refined the Nomad upload and data processing. This GUI introduces
+        the *staging area* that allows you to observe your uploads processing and inspect
+        the uploaded data before you decide to either publish your data or delete/upload
+        again.
+
+        Currently this is designed as just a complement to the original [Nomad Repository GUI](https://repository.nomad-coe.eu/NomadRepository-1.1).
+        You upload, process, inspect, and publish your data here. Here you have some
+        capabilities to search and explore uploaded dat. But to add comments, co-authors, and references,
+        create data-sets, and manage your account you still have to use the original [Nomad Repository GUI](https://repository.nomad-coe.eu/NomadRepository-1.1).
+
+        ### How to test the new upload
+
+        **!Please read this, before you explore this new part of Nomad!**
+
+        Try to explore this as *new user*. Travel through the menu on the left and just
+        use it. Feel free to upload data, look for limitations and things you do not like.
+        The goal should be to figure out what is wrong and missing.
+
+        Keep in mind that there are limitations:
+        * You can only login with users that already exist in the Nomad Repository. However,
+        you can use our test user: \`leonard.hofstadter@nomad-fairdi.tests.de\`, the password
+        is \`password\`.
+        * This is not yet connected to the actual Nomad Repository. Everything you upload
+        will only appear here and will be removed after this first testing period.
+        * Now all existing entries from the original Nomad appear in the search, since we
+        are still migrating data.
+
+        For feedback and any issues you find, feel free to open an issue [here](https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR/issues) or write
+        an email to [markus.scheidgen@physik.hu-berlin.de](mailto:markus.scheidgen@physik.hu-berlin.de).
+
+        ### Mid- and long term goals of nomad@FAIRDI
+        * more immediate and near-time use-modes (*staging area*, *code integration*, *on-site data*)
+        * 3rd parties run instances of the nomad on their servers (*mirrors*, *oasis*, *industry* usage)
+        * mirrors(partially) synchronize data with the central nomad instance (*data federation*)
+        * the nomad architecture/infrastructure is used for related *domains* (e.g. experimental material science) or even more unrelated domains
+        * nomad is integrated with existing *Open Data* initiatives and databases (*FAIRDI*, *EUDAT*, *optimade*)
+        * we benefit from nomad being *Open Source* (public git, outside participation)
+      `,
       entryLabel: 'calculation',
       searchPlaceholder: 'enter atoms, codes, functionals, or other quantity values',
       /**
@@ -101,6 +150,21 @@ class DomainProviderBase extends React.Component {
     },
     EMS: {
       name: 'EMS',
+      about: `
+        ## A Prototype for Experimental Material Science Data Sharing
+
+        The original goal of the NOMAD CoE project was to provide a data sharing and
+        publication platform for computational material science data. With this prototype,
+        we want to apply Nomad ideas and implementations to experimental material science
+        data.
+
+        As a first step, this site demonstrates Nomad's \`domain specific\` search interface
+        and how experiment (meta-)data can be represented. We want to explore what
+        meta-data exists for material experiments, what is necessary to provide meaningful
+        search capabilities, how we can implement FAIR data sharing principles, and
+        how can we establish a community process to integrate the various experimental
+        methods and respective data.
+      `,
       entryLabel: 'experiment',
       searchPlaceholder: 'enter atoms, experimental methods, or other quantity values',
       /**
diff --git a/gui/src/components/search/SearchPage.js b/gui/src/components/search/SearchPage.js
index ecd6251808..a62c5aba73 100644
--- a/gui/src/components/search/SearchPage.js
+++ b/gui/src/components/search/SearchPage.js
@@ -69,7 +69,7 @@ class SearchPage extends React.Component {
 
   state = {
     data: SearchPage.emptySearchData,
-    owner: 'all',
+    owner: 'migrated',
     searchState: {
       ...SearchAggregations.defaultState
     },
@@ -147,6 +147,7 @@ class SearchPage extends React.Component {
     const { pagination: { total }, metrics } = data
 
     const ownerLabel = {
+      migrated: 'Only migrated',
       all: 'All entries',
       public: 'Only public entries',
       user: 'Only your entries',
@@ -199,7 +200,7 @@ class SearchPage extends React.Component {
               <FormControl>
                 <FormLabel>Filter entries and show: </FormLabel>
                 <FormGroup row>
-                  {['all', 'public', 'user', 'staging'].map(owner => (
+                  {['migrated', 'all', 'public', 'user', 'staging'].map(owner => (
                     <FormControlLabel key={owner}
                       control={
                         <Checkbox checked={this.state.owner === owner} onChange={() => this.handleOwnerChange(owner)} value="owner" />
diff --git a/gui/src/components/uploads/Upload.js b/gui/src/components/uploads/Upload.js
index dc8201bb9f..6ce4336386 100644
--- a/gui/src/components/uploads/Upload.js
+++ b/gui/src/components/uploads/Upload.js
@@ -98,8 +98,13 @@ class Upload extends React.Component {
     const {page, perPage, orderBy, order} = params
     this.state.upload.get(page, perPage, orderBy, order === 'asc' ? 1 : -1)
       .then(upload => {
-        const {tasks_running, process_running, current_task} = upload
+        const {tasks_running, process_running, current_task, published} = upload
         if (!this._unmounted) {
+          if (published) {
+            this.setState({...params})
+            this.props.onDoesNotExist()
+            return
+          }
           const continueUpdating = tasks_running || process_running || current_task === 'uploading'
           this.setState({upload: upload, params: params, updating: continueUpdating})
           if (continueUpdating) {
diff --git a/nomad/api/repo.py b/nomad/api/repo.py
index a6889dfc9c..41d240cafe 100644
--- a/nomad/api/repo.py
+++ b/nomad/api/repo.py
@@ -186,7 +186,13 @@ class RepoCalcsResource(Resource):
         if order not in [-1, 1]:
             abort(400, message='invalid pagination')
 
-        if owner == 'all':
+        if owner == 'migrated':
+            # TODO this should be removed after migration
+            q = Q('term', published=True) & Q('term', with_embargo=False)
+            if g.user is not None:
+                q = q | Q('term', owners__user_id=g.user.user_id)
+            q = q & ~Q('term', **{'uploader.user_id': 1})  # pylint: disable=invalid-unary-operand-type
+        elif owner == 'all':
             q = Q('term', published=True) & Q('term', with_embargo=False)
             if g.user is not None:
                 q = q | Q('term', owners__user_id=g.user.user_id)
@@ -208,8 +214,9 @@ class RepoCalcsResource(Resource):
         else:
             abort(400, message='Invalid owner value. Valid values are all|user|staging, default is all')
 
-        with_provernance = not Q('term', **{'uploader.user_id': 1})
-        q = q & with_provernance if q is not None else with_provernance
+        # TODO this should be removed after migration
+        without_currupted_mainfile = ~Q('term', code_name='currupted mainfile')  # pylint: disable=invalid-unary-operand-type
+        q = q & without_currupted_mainfile if q is not None else without_currupted_mainfile
 
         data = dict(**request.args)
         data.pop('owner', None)
diff --git a/nomad/api/upload.py b/nomad/api/upload.py
index e0bcedaf54..5b86783b28 100644
--- a/nomad/api/upload.py
+++ b/nomad/api/upload.py
@@ -17,13 +17,14 @@ The upload API of the nomad@FAIRDI APIs. Provides endpoints to upload files and
 get the processing status of uploads.
 """
 
-from flask import g, request
+from flask import g, request, Response
 from flask_restplus import Resource, fields, abort
 from datetime import datetime
 from werkzeug.datastructures import FileStorage
 import os.path
 import os
 import io
+from functools import wraps
 
 from nomad import config, utils, files
 from nomad.processing import Upload, FAILURE
@@ -121,6 +122,7 @@ upload_operation_model = api.model('UploadOperation', {
 upload_metadata_parser = api.parser()
 upload_metadata_parser.add_argument('name', type=str, help='An optional name for the upload.', location='args')
 upload_metadata_parser.add_argument('local_path', type=str, help='Use a local file on the server.', location='args')
+upload_metadata_parser.add_argument('curl', type=bool, help='Provide a human readable message as body.', location='args')
 upload_metadata_parser.add_argument('file', type=FileStorage, help='The file to upload.', location='files')
 
 upload_list_parser = api.parser()
@@ -128,6 +130,46 @@ upload_list_parser.add_argument('all', type=bool, help='List all uploads, includ
 upload_list_parser.add_argument('name', type=str, help='Filter for uploads with the given name.', location='args')
 
 
+def disable_marshalling(f):
+    @wraps(f)
+    def wrapper(*args, **kwargs):
+        try:
+            return f(*args, **kwargs)
+        except DisableMarshalling as e:
+            print(e.un_marshalled)
+            return e.un_marshalled
+
+    return wrapper
+
+
+def marshal_with(*args, **kwargs):
+    """
+    A special version of the RESTPlus marshal_with decorator that allows to disable
+    marshalling at runtime by raising DisableMarshalling.
+    """
+    def decorator(func):
+        @api.marshal_with(*args, **kwargs)
+        def with_marshalling(*args, **kwargs):
+            return func(*args, **kwargs)
+
+        @wraps(with_marshalling)
+        def wrapper(*args, **kwargs):
+            try:
+                return with_marshalling(*args, **kwargs)
+            except DisableMarshalling as e:
+                print(e.un_marshalled)
+                return e.un_marshalled
+
+        return wrapper
+    return decorator
+
+
+class DisableMarshalling(Exception):
+    def __init__(self, body, status, headers):
+        super().__init__()
+        self.un_marshalled = Response(body, status=status, headers=headers)
+
+
 @ns.route('/')
 class UploadListResource(Resource):
     @api.doc('get_uploads')
@@ -146,8 +188,8 @@ class UploadListResource(Resource):
         return [upload for upload in Upload.user_uploads(g.user, **query_kwargs)], 200
 
     @api.doc('upload')
-    @api.marshal_with(upload_model, skip_none=True, code=200, description='Upload received')
     @api.expect(upload_metadata_parser)
+    @marshal_with(upload_model, skip_none=True, code=200, description='Upload received')
     @login_really_required
     @with_logger
     def put(self, logger):
@@ -234,7 +276,16 @@ class UploadListResource(Resource):
         upload.process_upload()
         logger.info('initiated processing')
 
-        return upload, 200
+        if bool(request.args.get('curl', False)):
+            raise DisableMarshalling(
+                '''
+Thanks for uploading your data to nomad.
+Go back to %s and press reload to see the progress on your upload and publish your data.
+
+''' % upload.gui_url,
+                200, {'Content-Type': 'text/plain; charset=utf-8'})
+        else:
+            return upload, 200
 
 
 class ProxyUpload:
@@ -410,7 +461,7 @@ class UploadCommandResource(Resource):
     @login_really_required
     def get(self):
         """ Get url and example command for shell based uploads. """
-        upload_url = '%s/uploads/' % config.api_url()
+        upload_url = '%s/uploads/?curl=True' % config.api_url()
 
         upload_command = 'curl -X PUT -H "X-Token: %s" "%s" -F file=@<local_file>' % (
             g.user.get_auth_token().decode('utf-8'), upload_url)
diff --git a/nomad/parsing/__init__.py b/nomad/parsing/__init__.py
index 2fe39941ea..abca23f6aa 100644
--- a/nomad/parsing/__init__.py
+++ b/nomad/parsing/__init__.py
@@ -358,7 +358,7 @@ parsers = [
         name='parsers/mpes', code_name='mpes', domain='EMS',
         parser_class_name='mpesparser.MPESParserInterface',
         mainfile_mime_re=r'(application/json)|(text/.*)',
-        mainfile_name_re=(r'.*_data.meta'),
+        mainfile_name_re=(r'.*.meta'),
         mainfile_contents_re=(r'"data_repository_name": "zenodo.org"')
     ),
     LegacyParser(
diff --git a/nomad/parsing/parser.py b/nomad/parsing/parser.py
index c2060a0adb..4a281e7d0f 100644
--- a/nomad/parsing/parser.py
+++ b/nomad/parsing/parser.py
@@ -85,7 +85,7 @@ class BrokenParser(Parser):
             decoded_buffer = buffer.decode('utf-8')
         except UnicodeDecodeError:
             # This file is binary, and should not be binary
-            return True
+            pass
         else:
             for pattern in self._patterns:
                 if pattern.search(decoded_buffer) is not None:
diff --git a/nomad/processing/data.py b/nomad/processing/data.py
index 6966243398..cc777817ec 100644
--- a/nomad/processing/data.py
+++ b/nomad/processing/data.py
@@ -703,6 +703,13 @@ class Upload(Proc):
     def join(self):
         self.cleanup()
 
+    @property
+    def gui_url(self):
+        base = config.api_url()[:-3]
+        if base.endswith('/'):
+            base = base[:-1]
+        return '%s/uploads/' % base
+
     @task
     def cleanup(self):
         search.refresh()
@@ -715,7 +722,7 @@ class Upload(Proc):
             '',
             'your data %suploaded %s has completed processing.' % (
                 self.name if self.name else '', self.upload_time.isoformat()),  # pylint: disable=no-member
-            'You can review your data on your upload page: %s/uploads' % config.api_url()[:-3]
+            'You can review your data on your upload page: %s' % self.gui_url
         ])
         try:
             infrastructure.send_mail(
-- 
GitLab