diff options
Diffstat (limited to 'uploader')
| -rw-r--r-- | uploader/background_jobs.py | 5 | ||||
| -rw-r--r-- | uploader/phenotypes/views.py | 77 | ||||
| -rw-r--r-- | uploader/route_utils.py | 12 | ||||
| -rw-r--r-- | uploader/static/css/layout-large.css | 66 | ||||
| -rw-r--r-- | uploader/static/css/layout-medium.css | 68 | ||||
| -rw-r--r-- | uploader/static/css/layout-small.css | 66 | ||||
| -rw-r--r-- | uploader/static/css/theme.css | 85 | ||||
| -rw-r--r-- | uploader/templates/background-jobs/default-success-page.html | 17 | ||||
| -rw-r--r-- | uploader/templates/base2.html | 111 | ||||
| -rw-r--r-- | uploader/templates/index2.html | 48 | ||||
| -rw-r--r-- | uploader/templates/phenotypes/view-dataset.html | 32 |
11 files changed, 568 insertions, 19 deletions
diff --git a/uploader/background_jobs.py b/uploader/background_jobs.py index dc9f837..d33c498 100644 --- a/uploader/background_jobs.py +++ b/uploader/background_jobs.py @@ -56,7 +56,7 @@ def register_job_handlers(job: str): return getattr(module, _parts[-1]) metadata = job["metadata"] - if metadata["success_handler"]: + if metadata.get("success_handler"): _success_handler = __load_handler__(metadata["success_handler"]) try: _error_handler = __load_handler__(metadata["error_handler"]) @@ -76,8 +76,7 @@ def handler(job: dict, handler_type: str) -> HandlerType: ).get(handler_type) if bool(_handler): return _handler(job) - raise Exception(# pylint: disable=[broad-exception-raised] - f"No '{handler_type}' handler registered for job type: {_job_type}") + return render_template("background-jobs/default-success-page.html", job=job) error_handler = partial(handler, handler_type="error") diff --git a/uploader/phenotypes/views.py b/uploader/phenotypes/views.py index ac36ec8..7002ccd 100644 --- a/uploader/phenotypes/views.py +++ b/uploader/phenotypes/views.py @@ -60,6 +60,7 @@ from .models import (dataset_by_id, datasets_by_population, phenotype_publication_data) +logger = logging.getLogger(__name__) phenotypesbp = Blueprint("phenotypes", __name__) render_template = make_template_renderer("phenotypes") @@ -233,11 +234,6 @@ def view_phenotype(# pylint: disable=[unused-argument] population["Id"], dataset["Id"], xref_id) - def __non_empty__(value) -> bool: - if isinstance(value, str): - return value.strip() != "" - return bool(value) - return render_template( "phenotypes/view-phenotype.html", species=species, @@ -1008,3 +1004,74 @@ def load_data_success( fragment=""))) except 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>/recompute-means", + methods=["POST"]) +@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 recompute_means(# pylint: disable=[unused-argument] + species: dict, + population: dict, + dataset: dict, + **kwargs +): + """Compute/Recompute the means for phenotypes in a particular population.""" + _jobs_db = app.config["ASYNCHRONOUS_JOBS_SQLITE_DB"] + _job_id = uuid.uuid4() + _xref_ids = tuple(int(item.split("_")[-1]) + for item in request.form.getlist("selected-phenotypes")) + + _loglevel = logging.getLevelName(app.logger.getEffectiveLevel()).lower() + command = [ + sys.executable, + "-u", + "-m", + "scripts.compute_phenotype_means", + app.config["SQL_URI"], + _jobs_db, + str(population["Id"]), + "--log-level", + _loglevel] + ( + ["--cross-ref-ids", ",".join(str(_id) for _id in _xref_ids)] + if len(_xref_ids) > 0 else + []) + logger.debug("%s.recompute_means: command (%s)", __name__, command) + + with sqlite3.connection(_jobs_db) as conn: + _job = gnlibs_jobs.launch_job( + gnlibs_jobs.initialise_job( + conn, + _job_id, + command, + "(re)compute-phenotype-means", + extra_meta={ + "species_id": species["SpeciesId"], + "population_id": population["Id"], + "dataset_id": dataset["Id"], + "success_handler": ( + "uploader.phenotypes.views." + "recompute_phenotype_means_success_handler") + }), + _jobs_db, + Path(f"{app.config['UPLOAD_FOLDER']}/job_errors"), + worker_manager="gn_libs.jobs.launcher", + loglevel=_loglevel) + return redirect(url_for("background-jobs.job_status", + job_id=_job["job_id"])) + + +def recompute_phenotype_means_success_handler(job): + """Handle loading new phenotypes into the database successfully.""" + flash("Means computed successfully!", "alert alert-success") + return redirect(url_for( + "species.populations.phenotypes.view_dataset", + species_id=job["metadata"]["species_id"], + population_id=job["metadata"]["population_id"], + dataset_id=job["metadata"]["dataset_id"], + job_id=job["job_id"])) diff --git a/uploader/route_utils.py b/uploader/route_utils.py index 63b2852..53247e6 100644 --- a/uploader/route_utils.py +++ b/uploader/route_utils.py @@ -1,5 +1,5 @@ """Generic routing utilities.""" -import json +import logging from json.decoder import JSONDecodeError from flask import (flash, @@ -15,6 +15,8 @@ from uploader.datautils import base64_encode_dict, base64_decode_to_dict from uploader.population.models import (populations_by_species, population_by_species_and_id) +logger = logging.getLogger(__name__) + def generic_select_population( # pylint: disable=[too-many-arguments, too-many-positional-arguments] species: dict, @@ -56,9 +58,9 @@ def redirect_to_next(default: dict): assert "uri" in default, "You must provide at least the 'uri' value." try: next_page = base64_decode_to_dict(request.args.get("next")) - return redirect(url_for( - next_page["uri"], - **{key:value for key,value in next_page.items()})) + _uri = next_page["uri"] + next_page.pop("uri") + return redirect(url_for(_uri, **next_page)) except (TypeError, JSONDecodeError) as _err: logger.debug("We could not decode the next value '%s'", next_page, @@ -66,7 +68,7 @@ def redirect_to_next(default: dict): return redirect(url_for( default["uri"], - **{key:value for key,value in default.items()})) + **{key:value for key,value in default.items() if key != "uri"})) def build_next_argument(uri: str, **kwargs) -> str: diff --git a/uploader/static/css/layout-large.css b/uploader/static/css/layout-large.css new file mode 100644 index 0000000..d1b3aa1 --- /dev/null +++ b/uploader/static/css/layout-large.css @@ -0,0 +1,66 @@ +* { + box-sizing: border-box; +} + +@media screen and (min-width: 20.1in) { + body { + display: grid; + grid-template-columns: 7fr 3fr; + grid-gap: 1em; + } + + #header { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Define layout for the children elements */ + display: grid; + grid-template-columns: 8fr 2fr; + } + + #header #header-text { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 2; + + /* Content styling */ + padding-left: 1em; + } + + #header #header-nav { + /* Place it in the parent element */ + grid-column-start: 2; + grid-column-end: 3; + } + + #main { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Define layout for the children elements */ + display: grid; + grid-template-columns: 7fr 3fr; + grid-gap: 1.5em; + } + + #main #breadcrumbs { + grid-column-start: 1; + grid-column-end: 3; + padding: 0 3px; + } + + #main #main-content { + /*background: #FFFFFF;*/ + /*max-width: 80%;*/ + + grid-column-start: 1; + grid-column-end: 2; + } + + #main #sidebar-content { + grid-column-start: 2; + grid-column-end: 3; + } +} diff --git a/uploader/static/css/layout-medium.css b/uploader/static/css/layout-medium.css new file mode 100644 index 0000000..f504073 --- /dev/null +++ b/uploader/static/css/layout-medium.css @@ -0,0 +1,68 @@ +* { + box-sizing: border-box; +} + +@media screen and (width > 8in) and (max-width: 20in) { + body { + display: grid; + grid-template-columns: 65fr 35fr; + grid-gap: 1em; + } + + #header { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Define layout for the children elements */ + display: grid; + grid-template-columns: 8fr 2fr; + } + + #header #header-text { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 2; + + /* Content styling */ + padding-left: 1em; + } + + #header #header-nav { + /* Place it in the parent element */ + grid-column-start: 2; + grid-column-end: 3; + } + + #main { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Define layout for the children elements */ + display: grid; + grid-template-columns: 7fr 3fr; + grid-gap: 5px; + } + + #main #breadcrumbs { + grid-column-start: 1; + grid-column-end: 3; + padding: 0 3px; + } + + #main #main-content { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 2; + grid-gap: 5px; + + /* Define layout for the children elements */ + max-width: 70%; + } + + #main #sidebar-content { + grid-column-start: 2; + grid-column-end: 3; + } +} diff --git a/uploader/static/css/layout-small.css b/uploader/static/css/layout-small.css new file mode 100644 index 0000000..cd32a71 --- /dev/null +++ b/uploader/static/css/layout-small.css @@ -0,0 +1,66 @@ +* { + box-sizing: border-box; +} + +@media screen and (max-width: 8in) { + body { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 1fr 2fr 7fr; + grid-gap: 1em; + } + + #header { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Define layout for the children elements */ + display: grid; + grid-template-columns: 1fr; + } + + #header #header-text { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 2; + + /* Content styling */ + padding-left: 1em; + } + + #header #header-nav { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 2; + } + + #main { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 2; + display: grid; + + /* Define layout for the children elements */ + grid-template-rows: 1.5em 80% 20%; + grid-template-columns: 1fr; + } + + #main #breadcrumbs { + grid-row-start: 1; + grid-row-end: 2; + + } + + #main #main-content { + grid-row-start: 2; + grid-row-end: 3; + } + + #main #sidebar-content { + grid-row-start: 3; + grid-row-end: 4; + + background: #E5E5FF; + } +} diff --git a/uploader/static/css/theme.css b/uploader/static/css/theme.css new file mode 100644 index 0000000..09e5a52 --- /dev/null +++ b/uploader/static/css/theme.css @@ -0,0 +1,85 @@ +body { + margin: 0.7em; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-size: 20px; + background: black; +} + +#header { + background-color: #336699; + color: #FFFFFF; + border-radius: 3px; + min-height: 30px; +} + +#header #header-nav .nav li a { + /* Content styling */ + color: #FFFFFF; + background: #4477AA; + border: solid 5px #336699; + border-radius: 5px; + font-size: 0.7em; + text-align: center; + padding: 1px 7px; +} + +#main #breadcrumbs { + background: #eaeaea; + border-radius:3px; + text-align: center; +} + +#main #main-content { + background: #e5e5ff; + + border-radius: 5px; + padding: 0 5px; +} + +#main #sidebar-content { + background: red; + + border-radius: 5px; + padding: 0 5px; +} + +#main .row { + margin: 0 2px; +} + + +.heading { + border-bottom: solid #EEBB88; + text-transform: capitalize; +} + +.subheading { + padding: 1em 0 0.1em 0.5em; + border-bottom: solid #88BBEE; + text-transform: capitalize; +} + +input[type="search"] { + border-radius: 5px; +} + +.btn { + text-transform: Capitalize; +} + +table.dataTable thead th, table.dataTable tfoot th{ + border-right: 1px solid white; + color: white; + background-color: #369 !important; +} + +table.dataTable tbody tr.selected td { + background-color: #ffee99 !important; +} + +.form-group { + margin-bottom: 2em; + padding-bottom: 0.2em; + border-bottom: solid gray 1px; +} diff --git a/uploader/templates/background-jobs/default-success-page.html b/uploader/templates/background-jobs/default-success-page.html new file mode 100644 index 0000000..5732456 --- /dev/null +++ b/uploader/templates/background-jobs/default-success-page.html @@ -0,0 +1,17 @@ +{%extends "phenotypes/base.html"%} +{%from "flash_messages.html" import flash_all_messages%} + +{%block title%}Background Jobs: Success{%endblock%} + +{%block pagetitle%}Background Jobs: Success{%endblock%} + +{%block contents%} +{{flash_all_messages()}} + +<div class="row"> + <p>Job <strong>{{job.job_id}}</strong>, + {%if job.get("metadata", {}).get("job-type")%} + of type '<em>{{job.metadata["job-type"]}}</em> + {%endif%}' completed successfully.</p> +</div> +{%endblock%} diff --git a/uploader/templates/base2.html b/uploader/templates/base2.html new file mode 100644 index 0000000..4c5d613 --- /dev/null +++ b/uploader/templates/base2.html @@ -0,0 +1,111 @@ +<!DOCTYPE html> +<html lang="en"> + + <head> + + <meta charset="UTF-8" /> + <meta application-name="GeneNetwork Quality-Control Application" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + {%block extrameta%}{%endblock%} + + <title>Data Upload and Quality Control: {%block title%}{%endblock%}</title> + + <link rel="stylesheet" type="text/css" + href="{{url_for('base.bootstrap', + filename='css/bootstrap.min.css')}}" /> + <link rel="stylesheet" type="text/css" + href="{{url_for('base.datatables', + filename='css/dataTables.bootstrap5.min.css')}}" /> + <link rel="stylesheet" type="text/css" href="/static/css/styles2.css" /> + <link rel="stylesheet" type="text/css" href="/static/css/layout-large.css" /> + <link rel="stylesheet" type="text/css" href="/static/css/layout-medium.css" /> + <link rel="stylesheet" type="text/css" href="/static/css/layout-small.css" /> + <link rel="stylesheet" type="text/css" href="/static/css/theme.css" /> + + {%block css%}{%endblock%} + + </head> + + <body> + <header id="header"> + <span id="header-text">GeneNetwork</span> + <nav id="header-nav"> + <ul class="nav justify-content-end"> + <li> + {%if user_logged_in()%} + <a href="{{url_for('oauth2.logout')}}" + title="Log out of the system"> + <span class="glyphicon glyphicon-user"></span> + {{user_email()}} Sign Out</a> + {%else%} + <a href="{{authserver_authorise_uri()}}" + title="Log in to the system">Sign In</a> + {%endif%} + </li> + </ul> + </nav> + </header> + + + <main id="main" class="main"> + <nav id="breadcrumbs" aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"> + <a href="{{url_for('base.index')}}">Home</a></li> + {%block extra_breadcrumbs%} + <li class="breadcrumb-item"> + <a href="{{url_for('base.index')}}">Page01</a></li> + <li class="breadcrumb-item"> + <a href="{{url_for('base.index')}}">Page02</a></li> + <li class="breadcrumb-item"> + <a href="{{url_for('base.index')}}">Page03</a></li> + <li class="breadcrumb-item"> + <a href="{{url_for('base.index')}}">Page04</a></li> + {%endblock%} + </ol> + </nav> + + <div id="main-content"> + {%block contents%}{%endblock%} + </div> + + <div id="sidebar-content"> + {%block sidebarcontents%}{%endblock%} + </div> + </main> + + + + <script type="text/javascript" src="/static/js/debug.js"></script> + <!-- + Core dependencies + --> + <script src="{{url_for('base.jquery', + filename='jquery.min.js')}}"></script> + <script src="{{url_for('base.bootstrap', + filename='js/bootstrap.min.js')}}"></script> + + <!-- + DataTables dependencies + --> + <script type="text/javascript" + src="{{url_for('base.datatables', + filename='js/dataTables.min.js')}}"></script> + <script type="text/javascript" + src="{{url_for('base.datatables_extensions', + filename='scroller/js/dataTables.scroller.min.js')}}"></script> + <script type="text/javascript" + src="{{url_for('base.datatables_extensions', + filename='buttons/js/dataTables.buttons.min.js')}}"></script> + <script type="text/javascript" + src="{{url_for('base.datatables_extensions', + filename='select/js/dataTables.select.min.js')}}"></script> + + <!-- + local dependencies + --> + <script type="text/javascript" src="/static/js/utils.js"></script> + <script type="text/javascript" src="/static/js/datatables.js"></script> + {%block javascript%}{%endblock%} + </body> +</html> diff --git a/uploader/templates/index2.html b/uploader/templates/index2.html new file mode 100644 index 0000000..72fea2f --- /dev/null +++ b/uploader/templates/index2.html @@ -0,0 +1,48 @@ +{%extends "base2.html"%} +{%from "flash_messages.html" import flash_all_messages%} + +{%block title%}Home{%endblock%} + +{%block pagetitle%}Home{%endblock%} + +{%block extra_breadcrumbs%}{%endblock%} + +{%block contents%} + +<div class="row">{{flash_all_messages()}}</div> + +{%if user_logged_in()%} +<div class="row"> + <p>Select from:</p> + <ul> + <li>Species</li> + <li>Publications</li> + </ul> +</div> +{%else%} +<div class="row"> + <p> + <a href="{{authserver_authorise_uri()}}" + title="Sign in to the system" + class="btn btn-primary">Sign in</a> + to continue.</p> +</div> +{%endif%} + +{%endblock%} + + + +{%block sidebarcontents%} +<div class="row"> + <form id="frm-quick-navigation"> + <legend>Quick Navigation</legend> + <div class="form-group"> + <label for="fqn-species-id">Species</label> + <select name="species_id"> + <option value="">Select species</option> + </select> + </div> + </form> +</div> +{%endblock%} diff --git a/uploader/templates/phenotypes/view-dataset.html b/uploader/templates/phenotypes/view-dataset.html index 306dcce..6a261fc 100644 --- a/uploader/templates/phenotypes/view-dataset.html +++ b/uploader/templates/phenotypes/view-dataset.html @@ -46,12 +46,32 @@ </div> <div class="row"> - <p><a href="{{url_for('species.populations.phenotypes.add_phenotypes', - species_id=species.SpeciesId, - population_id=population.Id, - dataset_id=dataset.Id)}}" - title="Add a bunch of phenotypes" - class="btn btn-primary">Add phenotypes</a></p> + <div class="col"> + <a href="{{url_for('species.populations.phenotypes.add_phenotypes', + species_id=species.SpeciesId, + population_id=population.Id, + dataset_id=dataset.Id)}}" + title="Add a bunch of phenotypes" + class="btn btn-primary">Add phenotypes</a> + </div> + + <div class="col"> + <form id="frm-recompute-phenotype-means" + method="POST" + action="{{url_for( + 'species.populations.phenotypes.recompute_means', + species_id=species['SpeciesId'], + population_id=population['Id'], + dataset_id=dataset['Id'])}}" + class="d-flex flex-row align-items-center flex-wrap" + style="display: inline;"> + <input type="submit" + title="Compute/Recompute the means for all phenotypes." + class="btn btn-info" + value="(rec/c)ompute means" + id="submit-frm-recompute-phenotype-means" /> + </form> + </div> </div> <div class="row"> |
