From 2ec7fdf4aba34f4482557b58e75a3e523261c176 Mon Sep 17 00:00:00 2001
From: Markus Scheidgen <markus.scheidgen@gmail.com>
Date: Tue, 30 Jul 2019 11:21:13 +0200
Subject: [PATCH] Added authors and comments to search.

---
 gui/src/components/search/SearchBar.js | 20 ++++++++++++++++++--
 nomad/api/repo.py                      | 18 +++++++++---------
 nomad/datamodel/base.py                |  4 ++--
 nomad/search.py                        |  4 +++-
 tests/test_api.py                      | 17 ++++++++++-------
 5 files changed, 42 insertions(+), 21 deletions(-)

diff --git a/gui/src/components/search/SearchBar.js b/gui/src/components/search/SearchBar.js
index c3888f28ff..b75826fb0c 100644
--- a/gui/src/components/search/SearchBar.js
+++ b/gui/src/components/search/SearchBar.js
@@ -127,9 +127,19 @@ class SearchBar extends React.Component {
     const { data: { quantities } } = this.props
     const suggestions = []
 
-    Object.keys(quantities).forEach(quantity => {
+    // filter out pseudo quantity total
+    const quantityKeys = Object.keys(quantities).filter(quantity => quantity !== 'total')
+
+    // put authors to the end
+    const authorIndex = quantityKeys.indexOf('authors')
+    if (authorIndex >= 0) {
+      quantityKeys[authorIndex] = quantityKeys.splice(quantityKeys.length - 1, 1, quantityKeys[authorIndex])[0]
+    }
+
+    quantityKeys.forEach(quantity => {
       Object.keys(quantities[quantity]).forEach(quantityValue => {
-        if (quantityValue.toLowerCase().startsWith(value)) {
+        const quantityValueLower = quantityValue.toLowerCase()
+        if (quantityValueLower.startsWith(value) || (quantity === 'authors' && quantityValueLower.includes(value))) {
           suggestions.push({
             key: quantity,
             value: quantityValue
@@ -138,6 +148,12 @@ class SearchBar extends React.Component {
       })
     })
 
+    // Always add as comment to the end of suggestions
+    suggestions.push({
+      key: 'comment',
+      value: value
+    })
+
     return suggestions
   }
 
diff --git a/nomad/api/repo.py b/nomad/api/repo.py
index 69f0aabbf6..e7c7d33720 100644
--- a/nomad/api/repo.py
+++ b/nomad/api/repo.py
@@ -17,6 +17,7 @@ The repository API of the nomad@FAIRDI APIs. Currently allows to resolve reposit
 meta-data.
 """
 
+from typing import List
 from flask_restplus import Resource, abort, fields
 from flask import request, g
 from elasticsearch_dsl import Q
@@ -110,7 +111,7 @@ repo_request_parser.add_argument(
 repo_request_parser.add_argument(
     'scroll_id', type=str, help='The id of the current scrolling window to use.')
 repo_request_parser.add_argument(
-    'metrics', type=str, help=(
+    'metrics', type=str, action='append', help=(
         'Metrics to aggregate over all quantities and their values as comma separated list. '
         'Possible values are %s.' % ', '.join(search.metrics_names)))
 
@@ -213,16 +214,11 @@ class RepoCalcsResource(Resource):
             page = int(request.args.get('page', 1))
             per_page = int(request.args.get('per_page', 10 if not scroll else 1000))
             order = int(request.args.get('order', -1))
-            metrics_str = request.args.get('metrics', '')
-
+            metrics: List[str] = request.args.getlist('metrics')
             from_time = rfc3339DateTime.parse(request.args.get('from_time', '2000-01-01'))
             until_time_str = request.args.get('until_time', None)
             until_time = rfc3339DateTime.parse(until_time_str) if until_time_str is not None else datetime.datetime.utcnow()
             time_range = (from_time, until_time)
-
-            metrics = [
-                metric for metric in metrics_str.split(',')
-                if metric in search.metrics_names]
         except Exception:
             abort(400, message='bad parameter types')
 
@@ -237,6 +233,10 @@ class RepoCalcsResource(Resource):
         if order not in [-1, 1]:
             abort(400, message='invalid pagination')
 
+        for metric in metrics:
+            if metric not in search.metrics_names:
+                abort(400, message='there is not metric %s' % metric)
+
         q = create_owner_query()
 
         # TODO this should be removed after migration
@@ -263,8 +263,8 @@ class RepoCalcsResource(Resource):
             return results, 200
         except search.ScrollIdNotFound:
             abort(400, 'The given scroll_id does not exist.')
-        except KeyError as e:
-            abort(400, str(e))
+        # except KeyError as e:
+        #     abort(400, str(e))
 
 
 repo_quantity_values_model = api.model('RepoQuantityValues', {
diff --git a/nomad/datamodel/base.py b/nomad/datamodel/base.py
index 34cdce2ab3..a5d2463596 100644
--- a/nomad/datamodel/base.py
+++ b/nomad/datamodel/base.py
@@ -230,7 +230,7 @@ class Domain:
 
     base_quantities = dict(
         authors=DomainQuantity(
-            elastic_field='authors.name.keyword', multi=True,
+            elastic_field='authors.name.keyword', multi=True, aggregations=1000,
             description=(
                 'Search for the given author. Exact keyword matches in the form "Lastname, '
                 'Firstname".')),
@@ -322,7 +322,7 @@ class Domain:
         """
         return {
             quantity.name: quantity.aggregations
-            for quantity in self.quantities.values()
+            for quantity in self.search_quantities.values()
             if quantity.aggregations > 0
         }
 
diff --git a/nomad/search.py b/nomad/search.py
index 8ea092f25f..59ad0586da 100644
--- a/nomad/search.py
+++ b/nomad/search.py
@@ -507,7 +507,8 @@ def metrics_search(
             order=dict(_key='asc'))
 
         buckets = search.aggs.bucket(quantity_name, terms)
-        add_metrics(buckets)
+        if quantity_name not in ['authors']:
+            add_metrics(buckets)
 
     add_metrics(search.aggs)
 
@@ -517,6 +518,7 @@ def metrics_search(
         result = {
             metric: bucket[metric]['value']
             for metric in metrics_to_use
+            if hasattr(bucket, metric)
         }
         result.update(code_runs=code_runs)
         return result
diff --git a/tests/test_api.py b/tests/test_api.py
index 4f67d039cd..fe378130f9 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -783,8 +783,8 @@ class TestRepo():
 
     @pytest.mark.parametrize('metrics', metrics_permutations)
     def test_search_total_metrics(self, client, example_elastic_calcs, no_warn, metrics):
-        rv = client.get('/repo/?metrics=%s' % ','.join(metrics))
-        assert rv.status_code == 200
+        rv = client.get('/repo/?%s' % urlencode(dict(metrics=metrics), doseq=True))
+        assert rv.status_code == 200, str(rv.data)
         data = json.loads(rv.data)
         total_metrics = data.get('quantities', {}).get('total', {}).get('all', None)
         assert total_metrics is not None
@@ -794,14 +794,17 @@ class TestRepo():
 
     @pytest.mark.parametrize('metrics', metrics_permutations)
     def test_search_aggregation_metrics(self, client, example_elastic_calcs, no_warn, metrics):
-        rv = client.get('/repo/?metrics=%s' % ','.join(metrics))
+        rv = client.get('/repo/?%s' % urlencode(dict(metrics=metrics), doseq=True))
         assert rv.status_code == 200
         data = json.loads(rv.data)
-        for quantities in data.get('quantities').values():
-            for metrics_result in quantities.values():
+        for name, quantity in data.get('quantities').items():
+            for metrics_result in quantity.values():
                 assert 'code_runs' in metrics_result
-                for metric in metrics:
-                    assert metric in metrics_result
+                if name != 'authors':
+                    for metric in metrics:
+                        assert metric in metrics_result
+                else:
+                    assert len(metrics_result) == 1  # code_runs is the only metric for authors
 
     @pytest.mark.parametrize('n_results, page, per_page', [(2, 1, 5), (1, 1, 1), (0, 2, 3)])
     def test_search_pagination(self, client, example_elastic_calcs, no_warn, n_results, page, per_page):
-- 
GitLab