From 4729abd0ab7a8fbeb700a278ac8bdfcf62ab79ac Mon Sep 17 00:00:00 2001
From: Frederick Muriuki Muriithi
Date: Sat, 10 Feb 2024 06:57:23 +0300
Subject: Collect and display errors on 'geno' files in the bundle.
---
qc_app/static/css/styles.css | 6 +-
qc_app/templates/rqtl2/rqtl2-qc-job-error.html | 49 +++++++++++++-
qc_app/upload/rqtl2.py | 6 ++
scripts/qc_on_rqtl2_bundle.py | 88 ++++++++++++++++----------
4 files changed, 110 insertions(+), 39 deletions(-)
diff --git a/qc_app/static/css/styles.css b/qc_app/static/css/styles.css
index 474c7f7..79dab0e 100644
--- a/qc_app/static/css/styles.css
+++ b/qc_app/static/css/styles.css
@@ -134,11 +134,7 @@ form fieldset:nth-child(odd) {
}
@media(min-width: 1250px) {
- form {
- width: 65ch;
- }
-
- .explainer {
+ form, .explainer, .error-table {
width: 65ch;
}
}
diff --git a/qc_app/templates/rqtl2/rqtl2-qc-job-error.html b/qc_app/templates/rqtl2/rqtl2-qc-job-error.html
index f9a912c..49df061 100644
--- a/qc_app/templates/rqtl2/rqtl2-qc-job-error.html
+++ b/qc_app/templates/rqtl2/rqtl2-qc-job-error.html
@@ -3,6 +3,31 @@
{%block title%}R/qtl2 bundle: QC Job Error{%endblock%}
+{%macro errors_table(tableid, errors)%}
+
+
+
+ Line |
+ Column |
+ Value |
+ Message |
+
+
+
+ {%for error in errors%}
+
+ {{error.line}} |
+ {{error.column}} |
+ {{error.value}} |
+ {{error.message}} |
+
+ {%endfor%}
+
+
+{%endmacro%}
+
{%block contents%}
R/qtl2 bundle: QC job Error
@@ -24,7 +49,29 @@
{%endif%}
-list other errors here by file type, I think …
+{%if errorsgeno | length > 0%}
+Geno Errors ({{errorsgeno | length}})
+
+ We found the following errors in the 'geno' file in your R/qtl2 bundle:
+
+{{errors_table("tbl-errors-geno", errorsgeno[0:50])}}
+{%endif%}
+
+{%if errorspheno | length > 0%}
+Pheno Errors ({{errorspheno | length}})
+
+ We found the following errors in the 'pheno' file in your R/qtl2 bundle:
+
+{{errorspheno}}
+{%endif%}
+
+{%if errorsphenocovar | length > 0%}
+Phenocovar Errors ({{errorsphenocovar | length}})
+
+ We found the following errors in the 'phenocovar' file in your R/qtl2 bundle:
+
+{{errorsphenocovar}}
+{%endif%}
stdout
{{cli_output(job, "stdout")}}
diff --git a/qc_app/upload/rqtl2.py b/qc_app/upload/rqtl2.py
index 4b00891..c45952c 100644
--- a/qc_app/upload/rqtl2.py
+++ b/qc_app/upload/rqtl2.py
@@ -207,6 +207,12 @@ def rqtl2_bundle_qc_status(jobid: UUID):
job=thejob,
errorsgeneric=json.loads(
thejob.get("errors-generic", "[]")),
+ errorsgeno=json.loads(
+ thejob.get("errors-geno", "[]")),
+ errorspheno=json.loads(
+ thejob.get("errors-pheno", "[]")),
+ errorsphenocovar=json.loads(
+ thejob.get("errors-phenocovar", "[]")),
messages=logmessages)
if jobstatus == "success":
jobmeta = json.loads(thejob["job-metadata"])
diff --git a/scripts/qc_on_rqtl2_bundle.py b/scripts/qc_on_rqtl2_bundle.py
index 43f766a..02c8c3a 100644
--- a/scripts/qc_on_rqtl2_bundle.py
+++ b/scripts/qc_on_rqtl2_bundle.py
@@ -1,7 +1,6 @@
"""Run Quality Control checks on R/qtl2 bundle."""
import sys
import json
-from pathlib import Path
from zipfile import ZipFile
from argparse import Namespace
from typing import Union, Sequence
@@ -9,6 +8,8 @@ from logging import Logger, getLogger, StreamHandler
from redis import Redis
+from quality_control.errors import InvalidValue
+
from qc_app import jobs
from qc_app.db_utils import database_connection
from qc_app.check_connections import check_db, check_redis
@@ -20,38 +21,59 @@ from scripts.cli_parser import init_cli_parser
from scripts.process_rqtl2_bundle import parse_job
from scripts.redis_logger import setup_redis_logger
-def add_to_errors(rconn: Redis, fqjobid: str, key: str, errors: Sequence[rqfe.MissingFile]):
+def dict2tuple(dct: dict) -> tuple:
+ """Utility to convert items in dicts to pairs of tuples."""
+ return tuple((key, val) for key,val in dct.items())
+
+def add_to_errors(rconn: Redis,
+ fqjobid: str,
+ key: str,
+ errors: Sequence[Union[InvalidValue, rqfe.MissingFile]]):
"""Add `errors` to a given list of errors"""
- errs = tuple(set(
- json.loads(rconn.hget(fqjobid, key) or "[]") +
- [error.message for error in errors]))
+ errs = tuple(dict(item) for item in set(
+ [dict2tuple(old) for old in
+ json.loads(rconn.hget(fqjobid, key) or "[]")] +
+ [dict2tuple({"type": type(error).__name__, **error._asdict()})
+ for error in errors]))
rconn.hset(fqjobid, key, json.dumps(errs))
-def qc_missing_files(rconn: Redis, fqjobid: str,
- bundlefilepath: Union[str, Path]) -> tuple[
- tuple[str, str], ...]:
+def qc_missing_files(rconn: Redis,
+ fqjobid: str,
+ zfile: ZipFile,
+ logger: Logger) -> bool:
"""Run QC for files listed in control file that don't exist in bundle."""
- with ZipFile(str(bundlefilepath), "r") as zfile:
- missing = rqc.missing_files(zfile)
- add_to_errors(rconn, fqjobid, "errors-generic", tuple(
- rqfe.MissingFile(
- mfile[0], mfile[1], (
- f"File '{mfile[1]}' is listed in the control file under "
- f"the '{mfile[0]}' key, but it does not actually exist in "
- "the bundle."))
- for mfile in missing))
-
- return missing
-
-def qc_geno_errors(_rconn, _fqjobid, _job) -> bool:
+ logger.info("Checking for missing files…")
+ missing = rqc.missing_files(zfile)
+ add_to_errors(rconn, fqjobid, "errors-generic", tuple(
+ rqfe.MissingFile(
+ mfile[0], mfile[1], (
+ f"File '{mfile[1]}' is listed in the control file under "
+ f"the '{mfile[0]}' key, but it does not actually exist in "
+ "the bundle."))
+ for mfile in missing))
+ if len(missing) > 0:
+ logger.error("Missing files in the bundle!")
+ return True
+ return False
+
+def qc_geno_errors(rconn, fqjobid, zfile, logger) -> bool:
"""Check for errors in `geno` file(s)."""
+ logger.info("Checking for geno errors…")
+ gerrs = tuple(rqc.geno_errors(zfile))
+ add_to_errors(rconn, fqjobid, "errors-generic", tuple(
+ err for err in gerrs if isinstance(err, rqfe.MissingFile)))
+ add_to_errors(rconn, fqjobid, "errors-geno", tuple(
+ err for err in gerrs if not isinstance(err, rqfe.MissingFile)))
+ if len(gerrs) > 0:
+ logger.error("The 'geno' file has errors.")
+ return True
return False
-def qc_pheno_errors(_rconn, _fqjobid, _job) -> bool:
+def qc_pheno_errors(_rconn, _fqjobid, _zfile, _logger) -> bool:
"""Check for errors in `pheno` file(s)."""
return False
-def qc_phenocovar_errors(_rconn, _fqjobid, _job) -> bool:
+def qc_phenocovar_errors(_rconn, _fqjobid, _zfile, _logger) -> bool:
"""Check for errors in `phenocovar` file(s)."""
return False
@@ -63,16 +85,16 @@ def run_qc(rconn: Redis,
thejob = parse_job(rconn, args.redisprefix, args.jobid)
jobmeta = thejob["job-metadata"]
- if len(qc_missing_files(rconn, fqjobid, jobmeta["rqtl2-bundle-file"])) > 0:
- logger.error("Missing files in the bundle!")
- return 1
-
- return (
- 1 if any((
- qc_geno_errors(rconn, fqjobid, thejob),
- qc_pheno_errors(rconn, fqjobid, thejob),
- qc_phenocovar_errors(rconn, fqjobid, thejob)))
- else 0)
+ with ZipFile(jobmeta["rqtl2-bundle-file"], "r") as zfile:
+ if qc_missing_files(rconn, fqjobid, zfile, logger):
+ return 1
+
+ return (
+ 1 if any((
+ qc_geno_errors(rconn, fqjobid, zfile, logger),
+ qc_pheno_errors(rconn, fqjobid, zfile, logger),
+ qc_phenocovar_errors(rconn, fqjobid, zfile, logger)))
+ else 0)
if __name__ == "__main__":
def main():
--
cgit v1.2.3