From 76481495847d27406b057963d5ed3c0238d33fd9 Mon Sep 17 00:00:00 2001
From: Stephan Porada <sporada@uni-bielefeld.de>
Date: Tue, 7 Jul 2020 15:08:15 +0200
Subject: [PATCH] Results import fixes and additions. Table creation rework.

---
 web/app/models.py                             |  20 ++++
 web/app/results/forms.py                      |  18 ++++
 web/app/results/tasks.py                      |  17 +++
 web/app/results/views.py                      | 102 +++++++++++++++++-
 web/app/services/forms.py                     |  18 ----
 web/app/services/views.py                     |  87 +--------------
 web/app/static/js/nopaque.lists.js            |  66 +++++++++++-
 web/app/templates/main/dashboard.html.j2      |   7 +-
 .../import_results.html.j2                    |   0
 .../templates/results/result_details.html.j2  |   3 +-
 web/app/templates/results/results.html.j2     |  70 ++++++++++++
 .../services/corpus_analysis.html.j2          |   3 +-
 web/app/templates/services/results.html.j2    |  52 ---------
 13 files changed, 296 insertions(+), 167 deletions(-)
 create mode 100644 web/app/results/tasks.py
 delete mode 100644 web/app/services/forms.py
 rename web/app/templates/{services => results}/import_results.html.j2 (100%)
 create mode 100644 web/app/templates/results/results.html.j2
 delete mode 100644 web/app/templates/services/results.html.j2

diff --git a/web/app/models.py b/web/app/models.py
index cc0c5691..ba165c3f 100644
--- a/web/app/models.py
+++ b/web/app/models.py
@@ -547,6 +547,16 @@ class Result (db.Model):
     file = db.relationship('ResultFile', backref='result', lazy='dynamic',
                            cascade='save-update, merge, delete')
 
+    def delete(self):
+        db.session.delete(self)
+        db.session.commit()
+
+    def __repr__(self):
+        '''
+        String representation of the Result. For human readability.
+        '''
+        return '<Result ID: {result_id}>'.format(result_id=self.id)
+
 
 class ResultFile(db.Model):
     '''
@@ -561,6 +571,16 @@ class ResultFile(db.Model):
     filename = db.Column(db.String(255))
     dir = db.Column(db.String(255))
 
+    def delete(self):
+        db.session.delete(self)
+        db.session.commit()
+
+    def __repr__(self):
+        '''
+        String representation of the ResultFile. For human readability.
+        '''
+        return '<ResultFile {result_file_name}>'.format(result_file_name=self.filename)  # noqa
+
 
 '''
 ' Flask-Login is told to use the application’s custom anonymous user by setting
diff --git a/web/app/results/forms.py b/web/app/results/forms.py
index e69de29b..13533ca6 100644
--- a/web/app/results/forms.py
+++ b/web/app/results/forms.py
@@ -0,0 +1,18 @@
+from flask_wtf import FlaskForm
+from werkzeug.utils import secure_filename
+from wtforms import FileField, SubmitField, ValidationError
+from wtforms.validators import DataRequired
+
+
+class ImportResultsForm(FlaskForm):
+    '''
+    Form used to import one result json file.
+    '''
+    file = FileField('File', validators=[DataRequired()])
+    submit = SubmitField()
+
+    def validate_file(self, field):
+        if not field.data.filename.lower().endswith('.json'):
+            raise ValidationError('File does not have an approved extension: '
+                                  '.json')
+        field.data.filename = secure_filename(field.data.filename)
diff --git a/web/app/results/tasks.py b/web/app/results/tasks.py
new file mode 100644
index 00000000..39e8e4be
--- /dev/null
+++ b/web/app/results/tasks.py
@@ -0,0 +1,17 @@
+from ..decorators import background
+from ..models import Result
+import os
+import shutil
+
+
+@background
+def delete_result(result_id, *args, **kwargs):
+    app = kwargs['app']
+    with app.app_context():
+        result = Result.query.get(result_id)
+        if result is None:
+            return
+        result_file_path = os.path.join(app.config['NOPAQUE_STORAGE'],
+                                        result.file[0].dir)
+        shutil.rmtree(result_file_path)
+        result.delete()  # cascades down and also deletes ResultFile
diff --git a/web/app/results/views.py b/web/app/results/views.py
index 0a8af940..4b591122 100644
--- a/web/app/results/views.py
+++ b/web/app/results/views.py
@@ -1,12 +1,94 @@
 from . import results
-from ..models import Result
-from flask import abort, render_template, current_app, request
-from flask_login import current_user, login_required
+from . import tasks
+from .. import db
 from ..corpora.forms import DisplayOptionsForm
+from ..models import Result, ResultFile, User
+from .forms import ImportResultsForm
+from datetime import datetime
+from flask import (abort, render_template, current_app, request, redirect,
+                   flash, url_for, make_response)
+from flask_login import current_user, login_required
 import json
 import os
 
 
+@results.route('/import_results', methods=['GET', 'POST'])
+@login_required
+def import_results():
+    '''
+    View to import one json result file. Uses the ImportReultFileForm.
+    '''
+    import_results_form = ImportResultsForm(prefix='add-result-file-form')
+    if import_results_form.is_submitted():
+        if not import_results_form.validate():
+            return make_response(import_results_form.errors, 400)
+        # Save the file
+        # result creation only happens on file save to avoid creating a result
+        # object in the db everytime by just visiting the import_results page
+        result = Result(user_id=current_user.id)
+        db.session.add(result)
+        db.session.commit()
+        if not (result.creator == current_user
+                or current_user.is_administrator()):
+            abort(403)
+        dir = os.path.join(str(result.user_id),
+                           'results',
+                           'corpus_analysis_results',
+                           str(result.id))
+        abs_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], dir)
+        abs_file_path = os.path.join(abs_dir,
+                                     import_results_form.file.data.filename)
+        os.makedirs(abs_dir)
+        import_results_form.file.data.save(abs_file_path)
+        # Saves all needed metadata entries in one json field
+        with open(abs_file_path, 'r') as f:
+            corpus_metadata = json.load(f)
+        del corpus_metadata['matches']
+        del corpus_metadata['cpos_lookup']
+        result_file = ResultFile(
+            result_id=result.id,
+            dir=dir,
+            filename=import_results_form.file.data.filename)
+        result.corpus_metadata = corpus_metadata
+        db.session.add(result_file)
+        db.session.commit()
+        flash('Result file added!', 'result')
+        return make_response(
+            {'redirect_url': url_for('results.results_overview')},
+            201)
+    return render_template('results/import_results.html.j2',
+                           import_results_form=import_results_form,
+                           title='Add corpus file')
+
+
+@results.route('/')
+@login_required
+def results_overview():
+    '''
+    Shows an overview of imported results.
+    '''
+    # get all results of current user
+    results = User.query.get(current_user.id).results
+
+    def __p_time(time_str):
+        # helper to convert the datetime into a nice readable string
+        return datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S.%f')
+    # convert results into a list of dicts to add the measier to list.js in
+    # the template
+    results = [dict(query=r.corpus_metadata['query'],
+                    match_count=r.corpus_metadata['match_count'],
+                    corpus_name=r.corpus_metadata['corpus_name'],
+                    corpus_creation_date=__p_time(r.corpus_metadata['corpus_creation_date']),  # noqa
+                    corpus_analysis_date=__p_time(r.corpus_metadata['corpus_analysis_date']),  # noqa
+                    corpus_type=r.corpus_metadata['corpus_type'],
+                    id=r.id)
+               for r in results]
+    return render_template('results/results.html.j2',
+                           title='Imported Results',
+                           # table=table,
+                           results=results)
+
+
 @results.route('/<int:result_id>/details')
 @login_required
 def result_details(result_id):
@@ -44,3 +126,17 @@ def result_inspect(result_id):
                            result=result,
                            result_json=result_json,
                            title='Result Insepct')
+
+
+@results.route('/<int:result_id>/delete')
+@login_required
+def result_delete(result_id):
+    result = Result.query.get_or_404(result_id)
+    if not result.id == result_id:
+        abort(404)
+    if not (result.creator == current_user
+            or current_user.is_administrator()):
+        abort(403)
+    tasks.delete_result(result_id)
+    flash('Result deleted!')
+    return redirect(url_for('results.results_overview'))
diff --git a/web/app/services/forms.py b/web/app/services/forms.py
deleted file mode 100644
index 13533ca6..00000000
--- a/web/app/services/forms.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from flask_wtf import FlaskForm
-from werkzeug.utils import secure_filename
-from wtforms import FileField, SubmitField, ValidationError
-from wtforms.validators import DataRequired
-
-
-class ImportResultsForm(FlaskForm):
-    '''
-    Form used to import one result json file.
-    '''
-    file = FileField('File', validators=[DataRequired()])
-    submit = SubmitField()
-
-    def validate_file(self, field):
-        if not field.data.filename.lower().endswith('.json'):
-            raise ValidationError('File does not have an approved extension: '
-                                  '.json')
-        field.data.filename = secure_filename(field.data.filename)
diff --git a/web/app/services/views.py b/web/app/services/views.py
index 9996e304..3c8d0b08 100644
--- a/web/app/services/views.py
+++ b/web/app/services/views.py
@@ -1,17 +1,13 @@
 from flask import (abort, current_app, flash, make_response, render_template,
                    url_for)
 from flask_login import current_user, login_required
-from .forms import ImportResultsForm
 from werkzeug.utils import secure_filename
 from . import services
 from .. import db
 from ..jobs.forms import AddFileSetupJobForm, AddNLPJobForm, AddOCRJobForm
-from ..models import Job, JobInput, Result, ResultFile, User
-from .tables import ResultTable, ResultItem
+from ..models import Job, JobInput
 import json
 import os
-import html
-from datetime import datetime
 
 
 SERVICES = {'corpus_analysis': {'name': 'Corpus analysis'},
@@ -85,84 +81,3 @@ def service(service):
     return render_template('services/{}.html.j2'.format(service),
                            title=SERVICES[service]['name'],
                            add_job_form=add_job_form)
-
-
-@services.route('/import_results', methods=['GET', 'POST'])
-@login_required
-def import_results():
-    '''
-    View to import one json result file. Uses the ImportReultFileForm.
-    '''
-    # TODO: Build in a check if uploaded json is actually a result file and
-    # not something different
-    # Add the possibility to add several result files at once.
-    import_results_form = ImportResultsForm(prefix='add-result-file-form')
-    if import_results_form.is_submitted():
-        if not import_results_form.validate():
-            return make_response(import_results_form.errors, 400)
-        # Save the file
-        # result creation only happens on file save to avoid creating a result
-        # object in the db everytime by just visiting the import_results page
-        result = Result(user_id=current_user.id)
-        db.session.add(result)
-        db.session.commit()
-        if not (result.creator == current_user
-                or current_user.is_administrator()):
-            abort(403)
-        dir = os.path.join(str(result.user_id),
-                           'results',
-                           'corpus_analysis_results',
-                           str(result.id))
-        abs_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], dir)
-        abs_file_path = os.path.join(abs_dir,
-                                     import_results_form.file.data.filename)
-        os.makedirs(abs_dir)
-        import_results_form.file.data.save(abs_file_path)
-        # Saves all needed metadata entries in one json field
-        with open(abs_file_path, 'r') as f:
-            corpus_metadata = json.load(f)
-        del corpus_metadata['matches']
-        del corpus_metadata['cpos_lookup']
-        result_file = ResultFile(
-            result_id=result.id,
-            dir=dir,
-            filename=import_results_form.file.data.filename)
-        result.corpus_metadata = corpus_metadata
-        db.session.add(result_file)
-        db.session.commit()
-        flash('Result file added!', 'result')
-        return make_response(
-            {'redirect_url': url_for('services.results')},
-            201)
-    return render_template('services/import_results.html.j2',
-                           import_results_form=import_results_form,
-                           title='Add corpus file')
-
-
-@services.route('/results')
-@login_required
-def results():
-    '''
-    Shows an overview of imported results.
-    '''
-    # get all results of current user
-    results = User.query.get(current_user.id).results
-    # create table row for every result#
-
-    def __p_time(time_str):
-        return datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S.%f')
-
-    items = [ResultItem(r.corpus_metadata['query'],
-                        r.corpus_metadata['match_count'],
-                        r.corpus_metadata['corpus_name'],
-                        __p_time(r.corpus_metadata['corpus_creation_date']),
-                        __p_time(r.corpus_metadata['corpus_analysis_date']),
-                        r.corpus_metadata['corpus_type'],
-                        r.id) for r in results]
-    # create table with items and save it as html
-    table = html.unescape(ResultTable(items).__html__())
-    # add class=list to table body with string replacement
-    table = table.replace('tbody', 'tbody class=list', 1)
-    return render_template('services/results.html.j2',
-                           title='Imported Results',
-                           table=table)
diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js
index b2ad4dcd..04145c2e 100644
--- a/web/app/static/js/nopaque.lists.js
+++ b/web/app/static/js/nopaque.lists.js
@@ -1,14 +1,21 @@
 class RessourceList extends List {
   constructor(idOrElement, subscriberList, type, options={}) {
-    if (!['corpus', 'job'].includes(type)) {
+    if (!["corpus", "job", "result"].includes(type)) {
       console.error("Unknown Type!");
       return;
     }
+    if (subscriberList) {
     super(idOrElement, {...RessourceList.options['common'],
                         ...RessourceList.options[type],
                         ...options});
     this.type = type;
     subscriberList.push(this);
+    } else {
+      super(idOrElement, {...RessourceList.options['extended'],
+                          ...RessourceList.options[type],
+                          ...options});
+      this.type = type;
+    }
   }
 
 
@@ -53,6 +60,8 @@ class RessourceList extends List {
     this.add(ressources.map(x => RessourceList.dataMapper[this.type](x)));
   }
 }
+
+
 RessourceList.dataMapper = {
   corpus: corpus => ({creation_date: corpus.creation_date,
                       description: corpus.description,
@@ -67,10 +76,35 @@ RessourceList.dataMapper = {
                 link: `/jobs/${job.id}`,
                 service: job.service,
                 status: job.status,
-                title: job.title})
+                title: job.title}),
+  result : result => ({ query: result.query,
+                        match_count: result.match_count,
+                        corpus_name: result.corpus_name,
+                        corpus_creation_date: result.corpus_creation_date,
+                        corpus_analysis_date: result.corpus_analysis_date,
+                        corpus_type : result.corpus_type,
+                        "details-link": `${result.id}/details`,
+                        "inspect-link": `${result.id}/inspect`,
+                        "delete-modal": `delete-result-${result.id}-modal`})
 };
+
+
 RessourceList.options = {
   common: {page: 4, pagination: {innerWindow: 8, outerWindow: 1}},
+  extended: {page: 10,
+                 pagination: [
+                 {
+                   name: "paginationTop",
+                   paginationClass: "paginationTop",
+                   innerWindow: 8,
+                   outerWindow: 1
+                 },
+                 {
+                    paginationClass: "paginationBottom",
+                    innerWindow: 8,
+                    outerWindow: 1
+                  }
+                ]},
   corpus: {item: `<tr>
                     <td>
                       <a class="btn-floating disabled">
@@ -122,7 +156,33 @@ RessourceList.options = {
                      {data: ["id"]},
                      {name: "link", attr: "href"},
                      {name: "service", attr: "data-service"},
-                     {name: "status", attr: "data-status"}]}
+                     {name: "status", attr: "data-status"}]},
+  result : {item: `<tr>
+                     <td class="query"></td>
+                     <td class="match_count"></td>
+                     <td class="corpus_name"></td>
+                     <td class="corpus_creation_date"></td>
+                     <td class="corpus_analysis_date"></td>
+                     <td class="corpus_type"></td>
+                     <td class="actions right-align">
+                      <a class="btn-floating details-link waves-effect waves-light"><i class="material-icons">info_outline</i>
+                      </a>
+                      <a class="btn-floating inspect-link waves-effect waves-light"><i class="material-icons">search</i>
+                      </a>
+                      <a class="btn-floating red delete-modal waves-effect waves-light modal-trigger"><i class="material-icons">delete</i>
+                      </a>
+                     </td>
+                   </tr>`,
+            valueNames: ["query",
+                         "match_count",
+                         "corpus_name",
+                         "corpus_creation_date",
+                         "corpus_analysis_date",
+                         "corpus_type",
+                         {name: "details-link", attr: "href"},
+                         {name: "inspect-link", attr: "href"},
+                         {name: "delete-modal", attr: "data-target"}]
+            }
 };
 
 
diff --git a/web/app/templates/main/dashboard.html.j2 b/web/app/templates/main/dashboard.html.j2
index 45569e6f..479f5b65 100644
--- a/web/app/templates/main/dashboard.html.j2
+++ b/web/app/templates/main/dashboard.html.j2
@@ -11,7 +11,7 @@
         <input id="search-corpus" class="search" type="search"></input>
         <label for="search-corpus">Search corpus</label>
       </div>
-      <table>
+      <table class="highlight">
         <thead>
           <tr>
             <th></th>
@@ -28,7 +28,8 @@
       <ul class="pagination"></ul>
     </div>
     <div class="card-action right-align">
-      <a class="waves-effect waves-light btn" href="{{ url_for('services.results') }}">Show Imported Results<i class="material-icons right">folder</i></a>
+      <a class="waves-effect waves-light btn" href="{{ url_for('results.import_results') }}">Import Results<i class="material-icons right">file_upload</i></a>
+      <a class="waves-effect waves-light btn" href="{{ url_for('results.results_overview') }}">Show Imported Results<i class="material-icons right">folder</i></a>
       <a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
     </div>
   </div>
@@ -44,7 +45,7 @@
         <input id="search-job" class="search" type="search"></input>
         <label for="search-job">Search job</label>
       </div>
-      <table>
+      <table class="highlight">
         <thead>
           <tr>
             <th><span class="sort" data-sort="service">Service</span></th>
diff --git a/web/app/templates/services/import_results.html.j2 b/web/app/templates/results/import_results.html.j2
similarity index 100%
rename from web/app/templates/services/import_results.html.j2
rename to web/app/templates/results/import_results.html.j2
diff --git a/web/app/templates/results/result_details.html.j2 b/web/app/templates/results/result_details.html.j2
index 73fe0f0a..7a85aa4a 100644
--- a/web/app/templates/results/result_details.html.j2
+++ b/web/app/templates/results/result_details.html.j2
@@ -63,7 +63,8 @@
       </table>
     </div>
     <div class="card-action right-align">
-      <a class="waves-effect waves-light btn" href="{{ url_for('services.import_results') }}">Inspect Results<i class="material-icons right">search</i></a>
+      <a class="waves-effect waves-light btn left-align" href="{{ url_for('results.results_overview') }}">Back To Overview<i class="material-icons right">arrow_back</i></a>
+      <a class="waves-effect waves-light btn" href="{{ url_for('results.result_inspect', result_id=result.id) }}">Inspect Results<i class="material-icons right">search</i></a>
     </div>
   </div>
 </div>
diff --git a/web/app/templates/results/results.html.j2 b/web/app/templates/results/results.html.j2
new file mode 100644
index 00000000..54ec3712
--- /dev/null
+++ b/web/app/templates/results/results.html.j2
@@ -0,0 +1,70 @@
+{% extends "nopaque.html.j2" %}
+
+{% set full_width = True %}
+
+{% block page_content %}
+
+<div class="col s12">
+  <p>This is an overview of all your imported results.</p>
+</div>
+
+<div class="col s12">
+  <div class="card">
+    <div class="card-content" id="results">
+      <div class="input-field">
+        <i class="material-icons prefix">search</i>
+        <input id="search-results" class="search" type="search"></input>
+        <label for="search-results">Search results</label>
+      </div>
+      <ul class="pagination paginationTop"></ul>
+      <table class="highlight responsive-table">
+        <thead>
+          <tr>
+            <th class="sort" data-sort="query">Query</th>
+            <th class="sort" data-sort="match_count">Match count</th>
+            <th class="sort" data-sort="corpus_name">Corpus name</th>
+            <th class="sort" data-sort="corpus_creation_date">Corpus creation date</th>
+            <th class="sort" data-sort="corpus_analysis_date">Corpus analysis date</th>
+            <th class="sort" data-sort="corpus_type">Corpus type</th>
+            <th>{# Actions #}</th>
+          </tr>
+        </thead>
+        <tbody class="list">
+          <tr class="show-if-only-child">
+            <td colspan="5">
+              <span class="card-title"><i class="material-icons left">folder</i>Nothing here...</span>
+              <p>No results yet improted.</p>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+      <ul class="pagination paginationBottom"></ul>
+    </div>
+    <div class="card-action right-align">
+      <a class="waves-effect waves-light btn" href="{{ url_for('results.import_results') }}">Import Results<i class="material-icons right">file_upload</i></a>
+    </div>
+  </div>
+</div>
+
+{# Delete modals #}
+{% for result in results %}
+<div id="delete-result-{{ result.id }}-modal" class="modal">
+  <div class="modal-content">
+    <h4>Confirm result file deletion</h4>
+    <p>Do you really want to delete the result file created on <i>{{ result.corpus_analysis_date }}</i>?
+      <p>The file holds results for the query <i>{{ result.query }}</i>.</p>
+    <p>The file will be permanently deleted!</p>
+  </div>
+  <div class="modal-footer">
+    <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
+    <a class="btn modal-close red waves-effect waves-light" href="{{ url_for('results.result_delete', result_id=result.id) }}"><i class="material-icons left">delete</i>Delete</a>
+  </div>
+</div>
+{% endfor %}
+
+<script>
+var ressources = {{ results|tojson|safe }};
+var importedResultsList = new RessourceList("results", null, "result");
+importedResultsList.addRessources(ressources);
+</script>
+{% endblock %}
diff --git a/web/app/templates/services/corpus_analysis.html.j2 b/web/app/templates/services/corpus_analysis.html.j2
index 2d6344ee..0d0a2193 100644
--- a/web/app/templates/services/corpus_analysis.html.j2
+++ b/web/app/templates/services/corpus_analysis.html.j2
@@ -36,7 +36,8 @@
       <ul class="pagination"></ul>
     </div>
     <div class="card-action right-align">
-      <a class="waves-effect waves-light btn" href="{{ url_for('services.results') }}">Show Imported Results<i class="material-icons right">folder</i></a>
+      <a class="waves-effect waves-light btn" href="{{ url_for('results.import_results') }}">Import Results<i class="material-icons right">file_upload</i></a>
+      <a class="waves-effect waves-light btn" href="{{ url_for('results.results_overview') }}">Show Imported Results<i class="material-icons right">folder</i></a>
       <a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
     </div>
   </div>
diff --git a/web/app/templates/services/results.html.j2 b/web/app/templates/services/results.html.j2
deleted file mode 100644
index 2b01d6fd..00000000
--- a/web/app/templates/services/results.html.j2
+++ /dev/null
@@ -1,52 +0,0 @@
-{% extends "nopaque.html.j2" %}
-
-{% block page_content %}
-
-<div class="col s12">
-  <p>This is an overview of all your imported results.</p>
-</div>
-
-<div class="col s12">
-  <div class="card">
-    <div class="card-content" id="results">
-      <div class="input-field">
-        <i class="material-icons prefix">search</i>
-        <input id="search-results" class="search" type="search"></input>
-        <label for="search-results">Search results</label>
-      </div>
-      <ul class="pagination paginationTop"></ul>
-      {{ table }}
-      <ul class="pagination paginationBottom"></ul>
-      <ul class="pagination"></ul>
-    </div>
-    <div class="card-action right-align">
-      <a class="waves-effect waves-light btn" href="{{ url_for('services.import_results') }}">Import Results<i class="material-icons right">file_upload</i></a>
-    </div>
-  </div>
-</div>
-
-<script>
-var options = {page: 10,
-               pagination: [
-               {
-                 name: "paginationTop",
-                 paginationClass: "paginationTop",
-                 innerWindow: 8,
-                 outerWindow: 1
-               },
-               {
-                  paginationClass: "paginationBottom",
-                  innerWindow: 8,
-                  outerWindow: 1
-                }
-               ],
-               valueNames: ['query',
-                            'match-count',
-                            'corpus-name',
-                            'corpus-creation-date',
-                            'corpus-analysis-date',
-                            'corpus-type']
-              };
-var resultsList = new List('results', options);
-</script>
-{% endblock %}
-- 
GitLab