about summary refs log tree commit diff
path: root/uploader
diff options
context:
space:
mode:
Diffstat (limited to 'uploader')
-rw-r--r--uploader/__init__.py3
-rw-r--r--uploader/phenotypes/views.py65
-rw-r--r--uploader/templates/phenotypes/job-status.html30
3 files changed, 89 insertions, 9 deletions
diff --git a/uploader/__init__.py b/uploader/__init__.py
index 46689c5..e00c726 100644
--- a/uploader/__init__.py
+++ b/uploader/__init__.py
@@ -136,7 +136,8 @@ def create_app(config: Optional[dict] = None):
         "uploader.flask_extensions",
         "uploader.publications.models",
         "uploader.publications.datatables",
-        "uploader.phenotypes.models"))
+        "uploader.phenotypes.models",
+        "uploader.phenotypes.views"))
 
     # setup jinja2 symbols
     app.add_template_global(user_logged_in)
diff --git a/uploader/phenotypes/views.py b/uploader/phenotypes/views.py
index ce73c89..23bc682 100644
--- a/uploader/phenotypes/views.py
+++ b/uploader/phenotypes/views.py
@@ -1,4 +1,6 @@
 """Views handling ('classical') phenotypes."""# pylint: disable=[too-many-lines]
+import io
+import csv
 import sys
 import uuid
 import json
@@ -21,12 +23,14 @@ from gn_libs import jobs as gnlibs_jobs
 from gn_libs.jobs.jobs import JobNotFound
 from gn_libs.mysqldb import database_connection
 
+from werkzeug.datastructures import Headers
 from flask import (flash,
                    request,
                    jsonify,
                    redirect,
                    Blueprint,
-                   current_app as app)
+                   current_app as app,
+                   Response as FlaskResponse)
 
 from r_qtl import r_qtl2_qc as rqc
 from r_qtl import exceptions as rqe
@@ -522,6 +526,65 @@ def job_status(
 
 @phenotypesbp.route(
     "<int:species_id>/populations/<int:population_id>/phenotypes/datasets"
+    "/<int:dataset_id>/job/<uuid:job_id>/download-errors",
+    methods=["GET"])
+@require_login
+@with_dataset(
+    species_redirect_uri="species.populations.phenotypes.index",
+    population_redirect_uri="species.populations.phenotypes.select_population",
+    redirect_uri="species.populations.phenotypes.list_datasets")
+def download_errors(
+        species: dict,
+        population: dict,
+        dataset: dict,
+        job_id: uuid.UUID,
+        **kwargs):# pylint: disable=[unused-argument]
+    """Download the list of errors as a CSV file."""
+    with Redis.from_url(app.config["REDIS_URL"], decode_responses=True) as rconn:
+        try:
+            job = jobs.job(rconn, jobs.jobsnamespace(), str(job_id))
+            _prefix_ = jobs.jobsnamespace()
+            _jobid_ = job['jobid']
+            def __generate_chunks__():
+                _errors_ = (
+                    json.loads(error)
+                    for key in rconn.keys(
+                            f"{_prefix_}:{str(_jobid_)}:*:errors:*")
+                    for error in rconn.lrange(key, 0, -1))
+                _chunk_no_ = 0
+                _all_errors_printed_ = False
+                while not _all_errors_printed_:
+                    _chunk_ = []
+                    try:
+                        for _ in range(0, 1000):
+                            _chunk_.append(next(_errors_))
+                    except StopIteration:
+                        _all_errors_printed_ = True
+                        if len(_chunk_) <= 0:
+                            raise
+
+                    _out_ = io.StringIO()
+                    _writer_ = csv.DictWriter(_out_, fieldnames=tuple(_chunk_[0].keys()))
+                    if _chunk_no_ == 0:
+                        _writer_.writeheader()
+                    _writer_.writerows(_chunk_)
+                    _chunk_no_ += 1
+                    yield _out_.getvalue()
+                    if _all_errors_printed_:
+                        return
+
+            headers = Headers()
+            headers.set("Content-Disposition",
+                        "attachment",
+                        filename=f"{job['job-type']}_{job['jobid']}.csv")
+            return FlaskResponse(
+                __generate_chunks__(), mimetype="text/csv", headers=headers)
+        except jobs.JobNotFound as _jnf:
+            return render_template("jobs/job-not-found.html", job_id=job_id)
+
+
+@phenotypesbp.route(
+    "<int:species_id>/populations/<int:population_id>/phenotypes/datasets"
     "/<int:dataset_id>/job/<uuid:job_id>/review",
     methods=["GET"])
 @require_login
diff --git a/uploader/templates/phenotypes/job-status.html b/uploader/templates/phenotypes/job-status.html
index 0bbe8e0..951907f 100644
--- a/uploader/templates/phenotypes/job-status.html
+++ b/uploader/templates/phenotypes/job-status.html
@@ -52,10 +52,10 @@
   <p>
     {%if errors | length == 0%}
     <a href="{{url_for('species.populations.phenotypes.review_job_data',
-           species_id=species.SpeciesId,
-           population_id=population.Id,
-           dataset_id=dataset.Id,
-           job_id=job_id)}}"
+             species_id=species.SpeciesId,
+             population_id=population.Id,
+             dataset_id=dataset.Id,
+             job_id=job_id)}}"
        class="btn btn-primary"
        title="Continue to process data">Continue</a>
     {%else%}
@@ -70,13 +70,28 @@
 </div>
 
 <h3 class="subheading">upload errors</h3>
+{%if errors | length == 0 %}
 <div class="row" style="max-height: 20em; overflow: scroll;">
-  {%if errors | length == 0 %}
   <p class="text-info">
     <span class="glyphicon glyphicon-info-sign"></span>
     No errors found so far
   </p>
-  {%else%}
+</div>
+{%else%}
+{%if errors | length > 0%}
+<div class="row">
+  <div class="col">
+    <a href="{{url_for('species.populations.phenotypes.download_errors',
+             species_id=species.SpeciesId,
+             population_id=population.Id,
+             dataset_id=dataset.Id,
+             job_id=job_id)}}"
+       class="btn btn-info"
+       title="Download the errors as a CSV file.">download errors CSV</a>
+  </div>
+</div>
+{%endif%}
+<div class="row" style="max-height: 20em; overflow: scroll;">
   <table class="table table-responsive">
     <thead style="position: sticky; top: 0; background: white;">
       <tr>
@@ -111,7 +126,8 @@
       {%endfor%}
     </tbody>
   </table>
-  {%endif%}
+</div>
+{%endif%}
 </div>
 
 <div class="row">