diff options
Diffstat (limited to 'bh20simplewebuploader')
19 files changed, 380 insertions, 284 deletions
diff --git a/bh20simplewebuploader/main.py b/bh20simplewebuploader/main.py index 77b3832..206f884 100644 --- a/bh20simplewebuploader/main.py +++ b/bh20simplewebuploader/main.py @@ -8,7 +8,7 @@ import os import sys import re import string -import yaml +import ruamel.yaml as yaml import pkg_resources from flask import Flask, request, redirect, send_file, send_from_directory, render_template, jsonify import os.path @@ -16,6 +16,9 @@ import requests import io import arvados from markupsafe import Markup +from schema_salad.sourceline import add_lc_filename +from schema_salad.schema import shortname +from typing import MutableSequence, MutableMapping ARVADOS_API = 'lugli.arvadosapi.com' ANONYMOUS_TOKEN = '5o42qdxpxp5cj15jqjf7vnxx5xduhm4ret703suuoa3ivfglfh' @@ -47,6 +50,8 @@ def type_to_heading(type_name): Turn a type name like "sampleSchema" from the metadata schema into a human-readable heading. """ + type_name = shortname(type_name) + print(type_name,file=sys.stderr) # Remove camel case decamel = re.sub('([A-Z])', r' \1', type_name) @@ -78,7 +83,7 @@ def is_iri(string): return string.startswith('http') -def generate_form(schema, options): +def generate_form(components, options): """ Linearize the schema into a list of dicts. @@ -101,9 +106,6 @@ def generate_form(schema, options): IRI. """ - # Get the list of form components, one of which is the root - components = schema.get('$graph', []) - # Find the root root_name = None # And also index components by type name @@ -131,55 +133,54 @@ def generate_form(schema, options): # First make a heading, if we aren't the very root of the form yield {'heading': type_to_heading(type_name)} - for field_name, field_type in by_name.get(type_name, {}).get('fields', {}).items(): + for field in by_name.get(type_name, {}).get('fields', []): + field_name = shortname(field["name"]) + field_type = field["type"] # For each field ref_iri = None docstring = None - if not isinstance(field_type, str): - # If the type isn't a string - - # It may have documentation - docstring = field_type.get('doc', None) - - # See if it has a more info/what goes here URL - predicate = field_type.get('jsonldPredicate', {}) - # Predicate may be a URL, a dict with a URL in _id, maybe a - # dict with a URL in _type, or a dict with _id and _type but no - # URLs anywhere. Some of these may not technically be allowed - # by the format, but if they occur, we might as well try to - # handle them. - if isinstance(predicate, str): - if is_iri(predicate): - ref_iri = predicate - else: - # Assume it's a dict. Look at the fields we know about. - for field in ['_id', 'type']: - field_value = predicate.get(field, None) - if isinstance(field_value, str) and is_iri(field_value) and ref_iri is None: - # Take the first URL-looking thing we find - ref_iri = field_value - break - - # Now overwrite the field type with the actual type string - field_type = field_type.get('type', '') - - # Decide if the field is optional (type ends in ?) optional = False - if field_type.endswith('?'): - # It's optional - optional = True - # Drop the ? - field_type = field_type[:-1] - - # Decide if the field is a list (type ends in []) is_list = False - if field_type.endswith('[]'): - # It's a list - is_list = True - # Reduce to the normal type - field_type = field_type[:-2] + + # It may have documentation + docstring = field.get('doc', None) + + # See if it has a more info/what goes here URL + predicate = field.get('jsonldPredicate', {}) + # Predicate may be a URL, a dict with a URL in _id, maybe a + # dict with a URL in _type, or a dict with _id and _type but no + # URLs anywhere. Some of these may not technically be allowed + # by the format, but if they occur, we might as well try to + # handle them. + if isinstance(predicate, str): + if is_iri(predicate): + ref_iri = predicate + else: + # Assume it's a dict. Look at the fields we know about. + for field in ['_id', 'type']: + field_value = predicate.get(field, None) + if isinstance(field_value, str) and is_iri(field_value) and ref_iri is None: + # Take the first URL-looking thing we find + ref_iri = field_value + break + + if isinstance(field_type, MutableSequence): + if field_type[0] == "null" and len(field_type) == 2: + optional = True + field_type = field_type[1] + else: + raise Exception("Can't handle it") + + if isinstance(field_type, MutableMapping): + if field_type["type"] == "array": + # Now replace the field type with the actual type string + is_list = True + field_type = field_type.get('items', '') + else: + field_type = field_type.get('type', '') + pass if field_type in by_name: # This is a subrecord. We need to recurse @@ -227,15 +228,24 @@ def generate_form(schema, options): return list(walk_fields(root_name)) -# At startup, we need to load the metadata schema from the uploader module, so we can make a form for it -if os.path.isfile("bh20sequploader/bh20seq-schema.yml"): - METADATA_SCHEMA = yaml.safe_load(open("bh20sequploader/bh20seq-schema.yml","r").read()) - METADATA_OPTION_DEFINITIONS = yaml.safe_load(open("bh20sequploader/bh20seq-options.yml","r").read()) -else: - METADATA_SCHEMA = yaml.safe_load(pkg_resources.resource_stream("bh20sequploader", "bh20seq-schema.yml")) - METADATA_OPTION_DEFINITIONS = yaml.safe_load(pkg_resources.resource_stream("bh20sequploader", "bh20seq-options.yml")) -# print(METADATA_SCHEMA,file=sys.stderr) -FORM_ITEMS = generate_form(METADATA_SCHEMA, METADATA_OPTION_DEFINITIONS) +import schema_salad.schema +def load_schema_generate_form(): + # At startup, we need to load the metadata schema from the uploader module, so we can make a form for it + if os.path.isfile("bh20sequploader/bh20seq-schema.yml"): + METADATA_SCHEMA = yaml.round_trip_load(open("bh20sequploader/bh20seq-schema.yml","r").read()) + METADATA_OPTION_DEFINITIONS = yaml.safe_load(open("bh20sequploader/bh20seq-options.yml","r").read()) + else: + METADATA_SCHEMA = yaml.round_trip_load(pkg_resources.resource_stream("bh20sequploader", "bh20seq-schema.yml")) + METADATA_OPTION_DEFINITIONS = yaml.safe_load(pkg_resources.resource_stream("bh20sequploader", "bh20seq-options.yml")) + + METADATA_SCHEMA["name"] = "bh20seq-schema.yml" + add_lc_filename(METADATA_SCHEMA, "bh20seq-schema.yml") + metaschema_names, _metaschema_doc, metaschema_loader = schema_salad.schema.get_metaschema() + schema_doc, schema_metadata = metaschema_loader.resolve_ref(METADATA_SCHEMA, "") + + return generate_form(schema_doc, METADATA_OPTION_DEFINITIONS) + +FORM_ITEMS = load_schema_generate_form() @app.route('/') def send_home(): @@ -243,7 +253,7 @@ def send_home(): Send the front page. """ - return render_template('home.html', menu='HOME') + return render_template('home.html', menu='HOME', load_map=True) @app.route('/upload') @@ -435,12 +445,12 @@ def receive_files(): if result.returncode != 0: # It didn't work. Complain. - error_message="Uploader returned value {} and said:".format(result.returncode) + str(result.stderr.decode('utf-8')) + error_message="Uploader returned value {} and said:\n".format(result.returncode) + str(result.stderr.decode('utf-8')) print(error_message, file=sys.stderr) return (render_template('error.html', error_message=error_message), 403) else: # It worked. Say so. - return render_template('success.html', log=result.stdout.decode('utf-8', errors='replace')) + return render_template('success.html', log=result.stderr.decode('utf-8', errors='replace')) finally: shutil.rmtree(dest_dir) @@ -479,10 +489,13 @@ def pending_table(output, items): for r in items: if r["status"] != "pending": continue - output.write("<tr>") - output.write("<td><a href='https://workbench.lugli.arvadosapi.com/collections/%s'>%s</a></td>" % (r["uuid"], r["uuid"])) - output.write("<td>%s</td>" % Markup.escape(r["sequence_label"])) - output.write("</tr>") + try: + output.write("<tr>") + output.write("<td><a href='https://workbench.lugli.arvadosapi.com/collections/%s'>%s</a></td>" % (r["uuid"], r["uuid"])) + output.write("<td>%s</td>" % Markup.escape(r.get("sequence_label"))) + output.write("</tr>") + except: + pass output.write( """ </table> @@ -497,18 +510,69 @@ def rejected_table(output, items): <th>Errors</th></tr> """) for r in items: - if r["status"] != "rejected": - continue + try: + if r["status"] != "rejected": + continue + output.write("<tr>") + output.write("<td><a href='https://workbench.lugli.arvadosapi.com/collections/%s'>%s</a></td>" % (r["uuid"], r["uuid"])) + output.write("<td>%s</td>" % Markup.escape(r.get("sequence_label"))) + output.write("<td><pre>%s</pre></td>" % Markup.escape("\n".join(r.get("errors", [])))) + output.write("</tr>") + except: + pass + output.write( +""" +</table> +""") + +def workflows_table(output, items): + output.write( +""" +<table> +<tr> +<th>Name</th> +<th>Sample id</th> +<th>Started</th> +<th>Container request</th> +</tr> +""") + for r in items: output.write("<tr>") - output.write("<td><a href='https://workbench.lugli.arvadosapi.com/collections/%s'>%s</a></td>" % (r["uuid"], r["uuid"])) - output.write("<td>%s</td>" % Markup.escape(r["sequence_label"])) - output.write("<td><pre>%s</pre></td>" % Markup.escape("\n".join(r.get("errors", [])))) + try: + sid = r["mounts"]["/var/lib/cwl/cwl.input.json"]["content"]["sample_id"] + output.write("<td>%s</td>" % Markup.escape(r["name"])) + output.write("<td>%s</td>" % Markup.escape(sid)) + output.write("<td>%s</td>" % Markup.escape(r["created_at"])) + output.write("<td><a href='https://workbench.lugli.arvadosapi.com/container_requests/%s'>%s</a></td>" % (r["uuid"], r["uuid"])) + except: + pass output.write("</tr>") output.write( """ </table> """) +def validated_table(output, items): + output.write( +""" +<table> +<tr> +<th>Collection</th> +<th>Sequence label</th> +</tr> +""") + for r in items: + try: + output.write("<tr>") + output.write("<td><a href='https://workbench.lugli.arvadosapi.com/collections/%s'>%s</a></td>" % (r["uuid"], r["uuid"])) + output.write("<td>%s</td>" % Markup.escape(r["properties"].get("sequence_label"))) + output.write("</tr>") + except: + pass + output.write( +""" +</table> +""") @app.route('/status') def status_page(): @@ -529,25 +593,42 @@ def status_page(): prop["uuid"] = p["uuid"] status[prop["status"]] = status.get(prop["status"], 0) + 1 + workflows = arvados.util.list_all(api.container_requests().list, + filters=[["name", "in", ["fastq2fasta.cwl"]], ["state", "=", "Committed"]], + order="created_at asc") + output = io.StringIO() validated = api.collections().list(filters=[["owner_uuid", "=", VALIDATED_PROJECT]], limit=1).execute() status["passed"] = validated["items_available"] - for s in (("passed", "/download"), ("pending", "#pending"), ("rejected", "#rejected")): + for s in (("passed", "/validated"), ("pending", "#pending"), ("rejected", "#rejected")): output.write("<p><a href='%s'>%s sequences QC %s</a></p>" % (s[1], status.get(s[0], 0), s[0])) - output.write("<a id='pending'><h1>Pending</h1>") + output.write("<p><a href='%s'>%s analysis workflows running</a></p>" % ('#workflows', len(workflows))) + + output.write("<a id='pending'><h1>Pending</h1></a>") pending_table(output, out) - output.write("<a id='rejected'><h1>Rejected</h1>") + output.write("<a id='rejected'><h1>Rejected</h1></a>") rejected_table(output, out) + output.write("<a id='workflows'><h1>Running Workflows</h1></a>") + workflows_table(output, workflows) + return render_template('status.html', table=Markup(output.getvalue()), menu='STATUS') +@app.route('/validated') +def validated_page(): + api = arvados.api(host=ARVADOS_API, token=ANONYMOUS_TOKEN, insecure=True) + output = io.StringIO() + validated = arvados.util.list_all(api.collections().list, filters=[["owner_uuid", "=", VALIDATED_PROJECT]]) + validated_table(output, validated) + return render_template('validated.html', table=Markup(output.getvalue()), menu='STATUS') + @app.route('/demo') def demo_page(): - return render_template('demo.html',menu='DEMO') + return render_template('demo.html',menu='DEMO',load_map=True) @app.route('/blog',methods=['GET']) def blog_page(): @@ -563,12 +644,6 @@ def about_page(): buf = get_html_body('doc/web/about.html','https://github.com/arvados/bh20-seq-resource/blob/master/doc/web/about.org') return render_template('about.html',menu='ABOUT',embed=buf) -## -@app.route('/map') -def map_page(): - return render_template('map.html',menu='DEMO') - - ## Dynamic API functions starting here ## This is quick and dirty for now, just to get something out and demonstrate the queries diff --git a/bh20simplewebuploader/static/image/curii.logo.ai.png b/bh20simplewebuploader/static/image/curii.logo.ai.png Binary files differnew file mode 100644 index 0000000..401afad --- /dev/null +++ b/bh20simplewebuploader/static/image/curii.logo.ai.png diff --git a/bh20simplewebuploader/static/image/curii.logo.ai.svg b/bh20simplewebuploader/static/image/curii.logo.ai.svg new file mode 100644 index 0000000..e87ea05 --- /dev/null +++ b/bh20simplewebuploader/static/image/curii.logo.ai.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg width="1333.3" height="1333.3" version="1.1" viewBox="0 0 1333.3 1333.3" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><defs><clipPath id="clipPath18"><path d="m0 1e3h1e3v-1e3h-1e3z"/></clipPath></defs><g transform="matrix(1.3333 0 0 -1.3333 0 1333.3)"><g clip-path="url(#clipPath18)"><g transform="translate(473.7 553.52)"><path d="m0 0c-23.91 0-41.063-17.932-41.063-44.182 0-26.251 17.153-44.179 41.063-44.179 17.414 0 31.448 5.714 40.805 16.63l16.63-16.63c-12.476-16.377-32.488-25.735-58.734-25.735-41.063 0-69.653 28.329-69.653 69.914 0 41.843 28.59 70.428 69.653 70.428 24.95 0 44.439-8.053 57.174-22.611l-16.37-19.229c-9.619 9.876-22.875 15.851-39.505 15.594" fill="#008a82"/></g><g transform="translate(681.34 578.73)"><path d="m0 0v-138.26h-29.889v27.029c-9.355-19.232-26.765-27.806-49.377-28.07-31.707 0-50.676 20.276-50.676 53.281v86.022h29.884v-77.707c0-20.792 12.216-33.526 32.228-33.526 23.911 0.523 37.941 18.972 37.941 43.143v68.09z" fill="#008a82"/></g><g transform="translate(796.73 579.77)"><path d="m0 0v-28.585c-27.809 1.556-45.481-14.815-47.818-38.468v-72.248h-29.887v138.26h29.887v-27.546c9.356 18.71 25.99 28.585 47.818 28.585" fill="#008a82"/></g><g transform="translate(847.15 618.24)"><path d="m0 0c0-10.136-7.54-17.673-17.412-17.673-9.877 0-17.413 7.537-17.413 17.673 0 10.396 7.536 17.933 17.413 17.933 9.872 0 17.412-7.537 17.412-17.933" fill="#f15a29"/></g><path d="m844.54 440.47h-29.625v138.26h29.625z" fill="#008a82"/><g transform="translate(914.46 618.24)"><path d="m0 0c0-10.136-7.541-17.673-17.413-17.673-9.876 0-17.413 7.537-17.413 17.673 0 10.396 7.537 17.933 17.413 17.933 9.872 0 17.413-7.537 17.413-17.933" fill="#f15a29"/></g><path d="m911.85 440.47h-29.625v138.26h29.625z" fill="#008a82"/><g transform="translate(131.67 492.85)"><path d="m0 0c0.628 4.633 3.756 8.256 7.824 9.843l-9.454 65.551c-0.213 0.021-0.426-0.01-0.638 0.021-2.008 0.27-3.807 1.046-5.367 2.126l-57.718-52.611c2.31-21.875 9.31-42.198 19.929-60.052l46.065 29.321c-0.641 1.805-0.924 3.766-0.641 5.801" fill="#f15a29"/></g><g transform="translate(135.54 569.04)"><path d="m0 0 9.451-65.545c0.223-0.018 0.432 0.01 0.648-0.02 0.975-0.131 1.887-0.395 2.767-0.737l38.262 56.827-44.955 14.619c-1.414-2.373-3.608-4.149-6.173-5.144" fill="#f15a29"/></g><g transform="translate(280.25 609.22)"><path d="m0 0-48.729-0.574c-1.211-5.586-6.039-9.562-11.655-9.744l-24.468-36.342 53.617-17.44c0 0.809-0.074 1.62-0.051 2.43 0.912 25.152 12.634 47.22 31.286 61.67" fill="#f15a29"/></g><g transform="translate(217.14 623.42)"><path d="m0 0 2.255 58.382c-4.958 0.183-9.984 0.112-15.047-0.205-15.084-0.972-29.49-4.186-42.934-9.259l51.399-50.633c1.31 0.834 2.768 1.411 4.327 1.715" fill="#f15a29"/></g><g transform="translate(132.75 592.86)"><path d="m0 0c6.582-0.901 11.22-6.828 10.639-13.386l46.505-15.128 24.123 35.815c-4.675 2.295-7.615 7.341-6.872 12.785 0.239 1.772 0.867 3.403 1.748 4.824l-53.093 52.29c-12.31-5.236-23.718-12.06-34.013-20.225l9.958-56.937c0.33-0.028 0.661 6e-3 1.005-0.038" fill="#f15a29"/></g><g transform="translate(135.28 482.35)"><path d="m0 0-46.12-29.351c14.73-22.781 35.474-41.144 59.841-53.023l-6.781 78.911c-2.738 0.383-5.104 1.664-6.94 3.463" fill="#008a82"/></g><g transform="translate(118.77 582.23)"><path d="m0 0c0.611 4.509 3.588 8.073 7.483 9.721l-9.4 53.816c-33.716-29.174-53.94-73.126-51.126-121.01l54.559 49.732c-1.283 2.272-1.897 4.952-1.516 7.74" fill="#f15a29"/></g><g transform="translate(361.67 546.89)"><path d="m0 0c-7.048-4.637-14.905-8.13-23.401-10.048-23.806-5.372-49.043-2.852-73.21-5.508 3.301-19.489 10.484-39.441 11.517-59.119 1.522-29.071-4.033-57.766-26.257-78.143-2.545-2.344-5.248-4.393-8.036-6.28 72.262 13.991 124.59 79.812 119.73 155.21-0.078 1.306-0.227 2.592-0.345 3.888" fill="#008a82"/></g><g transform="translate(250.67 529.01)"><path d="m0 0c-8.863-1.971-17.477-5.104-25.76-9.947 7.733-9.046 15.469-18.091 23.188-27.143 4.901-5.729-4.405-12.76-9.275-7.059-7.844 9.184-15.692 18.355-23.533 27.543-3.244-2.674-6.116-5.496-8.563-8.494 13.566-16.281 27.111-32.576 40.666-48.876 4.826-5.793-4.486-12.83-9.286-7.061-12.67 15.225-25.348 30.468-38.019 45.701-2.14-4.209-3.767-8.604-4.84-13.168 8.495-9.946 16.998-19.892 25.493-29.843 4.898-5.729-4.411-12.758-9.281-7.061-6.019 7.032-12.026 14.071-18.048 21.114 0 0-4.766-16.237-0.865-52.291-1.66 0.746-3.085 2.109-3.847 4.189-9.88 27.309-16.654 62.017 0.85 88.074 12.678 18.854 32.288 30.459 53.735 36.456l-51.115 16.627-39.174-58.167c2.461-2.63 3.791-6.261 3.274-10.104-0.661-4.881-4.104-8.68-8.509-10.1l7.041-82.087c11.054-4.806 22.783-8.293 34.971-10.297 31.35 2.055 62.177 14.151 70.26 48.559 4.169 17.775 3.976 36.163 0.456 54.069-2.558 13.035-7.277 26.098-9.819 39.366" fill="#008a82"/></g><g transform="translate(312.28 562.6)"><path d="m0 0c-9.515 0-17.227 7.709-17.227 17.228 0 9.514 7.712 17.227 17.227 17.227 9.519 0 17.228-7.713 17.228-17.227 0-9.519-7.709-17.228-17.228-17.228m-87.342 118.87-2.248-58.267c4.425-1.198 7.753-4.685 8.799-9.009l56.965 0.679c7.689 4.574 16.285 7.993 25.578 9.991 2.66 0.576 4.931-0.281 6.548-1.786l-2.677 1.573c-28.575-13.342-42.238-34.715-48.462-48.807-2.258-4.6-3.79-9.265-4.739-13.983-0.01-0.048-0.03-0.179-0.03-0.179-1.08-5.452-1.428-10.967-1.175-16.536 30.725 3.518 70.847-3.875 96.101 16.526-12.633 65.686-68.086 114.92-134.66 119.8" fill="#f15a29"/></g></g></g></svg>
\ No newline at end of file diff --git a/bh20simplewebuploader/static/main.css b/bh20simplewebuploader/static/main.css index 47fb408..6e651a4 100644 --- a/bh20simplewebuploader/static/main.css +++ b/bh20simplewebuploader/static/main.css @@ -47,7 +47,7 @@ h2 > svg { float: right; } -#map { +#mapid { width: 800px; height: 440px; border: 1px solid #AAA; @@ -178,7 +178,7 @@ span.dropt:hover {text-decoration: none; background: #ffffff; z-index: 6; } .about { display: grid; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: 1fr 1fr; grid-auto-flow: row; } @@ -229,7 +229,7 @@ a { #metadata_fill_form { column-count: 4; margin-top: 0.5em; - column-width: 250px; + column-width: 15em; } .record, .record .field-group, .record .field-group .field { @@ -238,6 +238,8 @@ a { -webkit-column-break-inside: avoid; /* Chrome, Safari, Opera */ page-break-inside: avoid; /* Firefox */ break-inside: avoid; + display: block; + width: 90%; } .record { @@ -258,6 +260,10 @@ a { width: max-content; } +.control { + width: 100%; +} + .filter-options { width: 100%; } @@ -304,9 +310,10 @@ footer { } .sponsors img { - width: 80%; - display:block; - margin:auto; + width: auto; + display: block; + margin: auto; + height: 4em; } .loader { @@ -378,6 +385,11 @@ div.status { border-bottom: 1px solid #ddd; } +.map { + padding: 20px 32px; + // display: inline-block; +} + .editbutton { float: right; text-align: right; diff --git a/bh20simplewebuploader/static/main.js b/bh20simplewebuploader/static/main.js index 4703047..1633c25 100644 --- a/bh20simplewebuploader/static/main.js +++ b/bh20simplewebuploader/static/main.js @@ -13,70 +13,41 @@ function myFunction() { } } -let map = L.map( 'map', { - center: [37.0902, -95.7129], // Default to U.S.A - minZoom: 3, - zoom: 0 -}); -L.tileLayer( 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>', - subdomains: ['a','b','c'] -}).addTo( map ); - -let markers = L.markerClusterGroup().addTo(map) - - function fetchAPI(apiEndPoint) { - fetch(scriptRoot + apiEndPoint) - .then(response => { - return response.json(); - }) - .then(data => { - console.log(data); - markers.clearLayers(); - document.getElementById("results").classList.remove("invisible"); - document.getElementById("loader").classList.add("invisible"); - if (!(apiEndPoint === "/api/getAllaccessions")) { - for (let i = 0; i < data.length; i++) { - let {"count": fastaCount, GPS, LocationLabel: label } = data[i]; - let coordinates = GPS.split(" "); - if (!(coordinates == null)) { - let lat, lon; - [lon, lat] = coordinates.map(parseFloat); - let point = L.point() - let marker = L.marker([lat, lon]); - marker.bindPopup("<b>" + label + "</b><br/>" + "FastaCount: " +fastaCount); - markers.addLayer(marker) - }} - } - // Reload the map - map.invalidateSize(); - }); - document.getElementById("results").classList.add("invisible"); - document.getElementById("loader").classList.remove("invisible"); - -} - -// Copy from function above but now added as table instead of plain json -function fetchAPIV2(apiEndPoint) { - fetch(scriptRoot + apiEndPoint) - .then(response => { - return response.json(); - }) - .then(data => { - console.log(data) - htmlString="<table>" - - // Depending on what we want to explore we'd have to call a different function ....? But how to Include that? - for (var i=0; i<data.length;i++) { - htmlString=htmlString+"<tr><td><a href='#' onclick='fetchSEQByLocation(\""+data[i]["key"]+"\");'>"+data[i]["label"]+"</a></td><td>"+data[i]["count"]+"<td></tr>" - } - htmlString=htmlString+"</table>" - - document.getElementById("table").innerHTML = htmlString - }); - - document.getElementById("results").classList.add("invisible"); + fetch(scriptRoot + apiEndPoint) + .then(response => { + return response.json(); + }) + .then(data => { + console.log(data); + }); + document.getElementById("map_view").classList.add("invisible"); + document.getElementById("loader").classList.remove("invisible"); +} + +// Copy from function above but now output HTML table instead of plain json +function fetchHTMLTable(apiEndPoint) { + fetch(scriptRoot + apiEndPoint) + .then(response => { + return response.json(); + }) + .then(data => { + console.log(data) + htmlString="<table>" + + // Depending on what we want to explore we'd have to call a different function ....? But how to Include that? + /* + for (var i=0; i<data.length;i++) { + htmlString=htmlString+"<tr><td><a href='#' onclick='fetchSEQByLocation(\""+data[i]["key"]+"\");'>"+data[i]["label"]+"</a></td><td>"+data[i]["count"]+"<td></tr>" + } +*/ + for (var i=0; i<data.length;i++) { + htmlString=htmlString+"<tr><td>"+data[i]["label"]+"</td><td>"+data[i]["count"]+"<td></tr>" + } + htmlString=htmlString+"</table>" + + document.getElementById("table").innerHTML = htmlString + }); } @@ -85,36 +56,39 @@ let search = () => { fetchAPI(scriptRoot + "/api/getDetailsForSeq?seq=" + encodeURIComponent(m)); } +// Get count from Arvados let fetchCount = () => { fetchAPI("/api/getCount"); } +// Get count from Virtuoso let fetchCountDB = () => { fetchAPI("/api/getCountDB"); } let fetchSEQCountBySpecimen = () => { - fetchAPIV2("/api/getSEQCountbySpecimenSource"); + fetchHTMLTable("/api/getSEQCountbySpecimenSource"); } let fetchSEQCountByLocation = () => { - fetchAPIV2("/api/getSEQCountbyLocation"); + fetchHTMLTable("/api/getSEQCountbyLocation"); } let fetchSEQCountByTech = () => { - fetchAPIV2("/api/getSEQCountbytech"); + fetchHTMLTable("/api/getSEQCountbytech"); } let fetchAllaccessions = () => { - fetchAPI("/api/getAllaccessions"); + fetchHTMLTable("/api/getAllaccessions"); }; -let fetchCountByGPS = () => { - fetchAPI("/api/getCountByGPS"); +let fetchMap = () => { + fetchAPI("/api/getCountByGPS"); + updateMapMarkers(); }; let fetchSEQCountbyLocation = () => { - fetchAPIV2("/api/getSEQCountbyLocation"); + fetchHTMLTable("/api/getSEQCountbyLocation"); }; let fetchSEQByLocation = () => { @@ -122,7 +96,7 @@ let fetchSEQByLocation = () => { }; let fetchSEQCountbyContinent = () => { - fetchAPIV2("/api/getSEQCountbyContinent"); + fetchHTMLTable("/api/getSEQCountbyContinent"); } @@ -252,36 +226,3 @@ function on_submit_button() { return false; } } - - - -// - -function drawMap(){ - -// initialize the map on the "map" div with a given center and zoom -var mymap = L.map('mapid').setView([51.505, -0.09], 1); - -L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' -}).addTo(mymap); - -fetch(scriptRoot + "api/getCountByGPS") - .then(response => { - console.log(response) - return response.json(); - }) - .then(data => { - - for (var i=0; i<data.length;i++) { - gps=data[i]["GPS"].split(" ") - var circle = L.circle([gps[1], gps[0]], { - color: 'red', - fillColor: '#f03', - fillOpacity: 0.5, - radius: parseInt(data[i]["count"]) //not working for whatever reason - }).addTo(mymap); - } - - }); -} diff --git a/bh20simplewebuploader/static/map.js b/bh20simplewebuploader/static/map.js new file mode 100644 index 0000000..1003f7d --- /dev/null +++ b/bh20simplewebuploader/static/map.js @@ -0,0 +1,50 @@ + +var map = L.map( 'mapid', { + center: [51.505, -0.09], // Default to U.S.A + minZoom: 2, + zoom: 0 +}); + +L.tileLayer( 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> | <a href="http://covid19.genenetwork.org/">COVID-19 PubSeq</a>', + subdomains: ['a','b','c'] +}).addTo(map); + + +function drawMap(){ + var mymap = map; + + fetch(scriptRoot + "api/getCountByGPS") + .then(response => { + console.log(response) + return response.json(); + }) + .then(data => { + updateMapMarkers(data); + + }); + document.getElementById("map_view").classList.remove("invisible"); + map.invalidateSize(); +} + + + +/* This function updates the map with markers + * +*/ +function updateMapMarkers(data) { + let markers = L.markerClusterGroup(); + for (let i = 0; i < data.length; i++) { + let {"count": fastaCount, GPS, LocationLabel: label } = data[i]; + let coordinates = GPS.split(" "); + if (!(coordinates == null)) { + let lat, lon; + [lon, lat] = coordinates.map(parseFloat); + let point = L.point() + marker = (L.marker([lat, lon])); + marker.bindPopup("<b>" + label + "</b><br/>" + "SARS-CoV-2<br/>sequences: " +fastaCount); + markers.addLayer(marker); + } + } + map.addLayer(markers); +} diff --git a/bh20simplewebuploader/templates/blog.html b/bh20simplewebuploader/templates/blog.html index 823f8a1..f4c2a85 100644 --- a/bh20simplewebuploader/templates/blog.html +++ b/bh20simplewebuploader/templates/blog.html @@ -63,6 +63,14 @@ We explore the Arvados command line and API </div> </div> + <div class="blog-table-row"> + <div class="blog-table-cell"> + <a href="/blog?id=using-covid-19-pubseq-part6">Prepare for uploading to EBI/ENA</a> + </div> + <div class="blog-table-cell"> + Generate the files needed for uploading to EBI/ENA + </div> + </div> </div> </div> </section> diff --git a/bh20simplewebuploader/templates/demo-run.html b/bh20simplewebuploader/templates/demo-run.html deleted file mode 100644 index a8f9edc..0000000 --- a/bh20simplewebuploader/templates/demo-run.html +++ /dev/null @@ -1,26 +0,0 @@ -<section class="search-section"> - <div class="filter-options" action="#"> - <p>[Demo] Display content sequences by: </p> - <div> - <button class="button" onclick="fetchSEQCountBySpecimen()">Count by Specimen source</button> - <button class="button" onclick="fetchSEQCountByLocation()">Count by Location</button> - <button class="button" onclick="fetchSEQCountByTech()">Count by Sequencer</button> - <button class="button" onclick="fetchAllaccessions()">Show All accessions</button> - <button class="button" onclick="fetchSEQCountbyContinent()">Count by Continent</button> - <button class="button" onclick="fetchCountByGPS()">Map</button> - - </div> - - </div> - -</section> -<div id="loader" class="loader invisible"> -</div> - -<section id="results" class="invisible"> - <div id="map"></div> -</section> - - <section> - <div id="table"></div> - </section> diff --git a/bh20simplewebuploader/templates/demo.html b/bh20simplewebuploader/templates/demo.html index 44aded0..75bc0e2 100644 --- a/bh20simplewebuploader/templates/demo.html +++ b/bh20simplewebuploader/templates/demo.html @@ -1,13 +1,51 @@ <!DOCTYPE html> <html> {% include 'header.html' %} + {% include 'mapheader.html' %} <body> {% include 'banner.html' %} {% include 'menu.html' %} - {% include 'search.html' %} - <p>The Virtuoso database contains <span id="CounterDB"></span> public sequences!</p> - {% include 'demo-run.html' %} - {% include 'footer.html' %} + + <p>The Virtuoso database contains <span id="CounterDB"></span> public sequences!</p> + + <!-- + <div class="search"> + <input id="search-input" type="search" placeholder="FASTA uri" required> + <button class="button search-button" type="submit" onclick="search()"> + <span class="icon ion-search"> + <span class="sr-only">Search</span> + </span> + </button> + <span class="dropt" title="http://collections.lugli.arvadosapi.com/c=00fede2c6f52b053a14edca01cfa02b7+126/sequence.fasta">(example)<span style="width:500px;"></span></span> + </div> + --> + + <section class="search-section"> + <div class="filter-options" action="#"> + <p>[Demo] Display content sequences by: </p> + <div> + <button class="button" onclick="fetchSEQCountBySpecimen()">Count by Specimen source</button> + <button class="button" onclick="fetchSEQCountByLocation()">Count by Location</button> + <button class="button" onclick="fetchSEQCountByTech()">Count by Sequencer</button> + <!-- <button class="button" onclick="fetchAllaccessions()">Show All accessions</button> --> + <button class="button" onclick="fetchSEQCountbyContinent()">Count by Continent</button> + </div> + + </div> + + </section> + <div id="loader" class="loader invisible"> + </div> + + <section id="map_view" class="map"> + <div id="mapid"></div> + </section> + + <section> + <div id="table"></div> + </section> + + {% include 'footer.html' %} <script type="text/javascript"> let scriptRoot = {{ request.script_root|tojson|safe }}; // examples @@ -24,7 +62,10 @@ }); }); + drawMap() + </script> + </body> </html> diff --git a/bh20simplewebuploader/templates/error.html b/bh20simplewebuploader/templates/error.html index b1d9402..fc08aed 100644 --- a/bh20simplewebuploader/templates/error.html +++ b/bh20simplewebuploader/templates/error.html @@ -15,7 +15,7 @@ </pre> </p> <p> - <a href="/">Click here to try again.</a> + <a href="/upload">Click here to try again.</a> </p> <hr> </body> diff --git a/bh20simplewebuploader/templates/footer.html b/bh20simplewebuploader/templates/footer.html index 37a6b64..f84cef5 100644 --- a/bh20simplewebuploader/templates/footer.html +++ b/bh20simplewebuploader/templates/footer.html @@ -21,12 +21,15 @@ <img src="static/image/covid19biohackathon.png"></a> </div> <div class="sponsorimg"> - <a href="https://www.commonwl.org/"><img src="static/image/CWL.png"></a> + <a href="https://www.curii.com/"><img src="static/image/curii.logo.ai.png"></a> </div> <div class="sponsorimg"> <a href="https://arvados.org/"><img src="static/image/arvados-logo.png"></a> </div> <div class="sponsorimg"> + <a href="https://www.commonwl.org/"><img src="static/image/CWL.png"></a> + </div> + <div class="sponsorimg"> <a href="https://uthsc.edu/"><img src="static/image/UTHSC-primary-stacked-logo-4c.png"></a> </div> <div class="sponsorimg"> @@ -44,6 +47,9 @@ </center> </div> </section> +{% if load_map %} +<script type="text/javascript" src="/static/map.js"></script> +{% endif %} <script type="text/javascript" src="/static/main.js"></script> <script type="text/javascript"> diff --git a/bh20simplewebuploader/templates/header.html b/bh20simplewebuploader/templates/header.html index 0ac5157..1d66590 100644 --- a/bh20simplewebuploader/templates/header.html +++ b/bh20simplewebuploader/templates/header.html @@ -6,22 +6,4 @@ {% if blog %} <link rel="Blog stylesheet" type="text/css" href="/static/blog.css" /> {% endif %} - <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" - integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" - crossorigin=""/> - <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" - integrity="sha512-RLEjtaFGdC4iQMJDbMzim/dOvAu+8Qp9sw7QE4wIMYcg2goVoivzwgSZq9CsIxp4xKAZPKh5J2f2lOko2Ze6FQ==" - crossorigin=""/> - - <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" - integrity="sha512-BBToHPBStgMiw0lD4AtkRIZmdndhB6aQbXpX7omcrXeG2PauGBl2lzq2xUZTxaLxYz5IDHlmneCZ1IJ+P3kYtQ==" - crossorigin=""/> - - <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" - integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" - crossorigin=""></script> - - <script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js" - integrity="sha512-MQlyPV+ol2lp4KodaU/Xmrn+txc1TP15pOBF/2Sfre7MRsA/pB4Vy58bEqe9u7a7DczMLtU5wT8n7OblJepKbg==" - crossorigin=""></script> </head> diff --git a/bh20simplewebuploader/templates/home.html b/bh20simplewebuploader/templates/home.html index b90a18d..bede611 100644 --- a/bh20simplewebuploader/templates/home.html +++ b/bh20simplewebuploader/templates/home.html @@ -1,6 +1,7 @@ <!DOCTYPE html> <html> {% include 'header.html' %} + {% include 'mapheader.html' %} <body> {% include 'banner.html' %} {% include 'menu.html' %} @@ -44,7 +45,19 @@ </div> </section> -{% include 'footer.html' %} + <section id="map_view" class="map"> + <div id="mapid"></div> + </section> + + {% include 'footer.html' %} + + + <script type="text/javascript"> + let scriptRoot = {{ request.script_root|tojson|safe }}; // examples + + drawMap() + + </script> </body> </html> diff --git a/bh20simplewebuploader/templates/map.html b/bh20simplewebuploader/templates/map.html deleted file mode 100644 index 595af0c..0000000 --- a/bh20simplewebuploader/templates/map.html +++ /dev/null @@ -1,33 +0,0 @@ -<!DOCTYPE html> -<html> - {% include 'header.html' %} -<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" - integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" - crossorigin=""/> - - {% include 'banner.html' %} - {% include 'menu.html' %} - <div id="mapid" style="height: 500px;"></div> - - {% include 'footer.html' %} - - - - - <script type="text/javascript"> - let scriptRoot = {{ request.script_root|tojson|safe }}; // examples - </script> - -<!-- Make sure you put this AFTER Leaflet's CSS --> - <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" - integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" - crossorigin=""></script> - - <script> - //drawMap - drawMap() - </script> - - </body> - -</html> diff --git a/bh20simplewebuploader/templates/mapheader.html b/bh20simplewebuploader/templates/mapheader.html new file mode 100644 index 0000000..ca62051 --- /dev/null +++ b/bh20simplewebuploader/templates/mapheader.html @@ -0,0 +1,16 @@ + <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" + integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" + crossorigin=""/> + <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" + integrity="sha512-RLEjtaFGdC4iQMJDbMzim/dOvAu+8Qp9sw7QE4wIMYcg2goVoivzwgSZq9CsIxp4xKAZPKh5J2f2lOko2Ze6FQ==" + crossorigin=""/> + <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" + integrity="sha512-BBToHPBStgMiw0lD4AtkRIZmdndhB6aQbXpX7omcrXeG2PauGBl2lzq2xUZTxaLxYz5IDHlmneCZ1IJ+P3kYtQ==" + crossorigin=""/> + + <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" + integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" + crossorigin=""></script> + <script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js" + integrity="sha512-MQlyPV+ol2lp4KodaU/Xmrn+txc1TP15pOBF/2Sfre7MRsA/pB4Vy58bEqe9u7a7DczMLtU5wT8n7OblJepKbg==" + crossorigin=""></script> diff --git a/bh20simplewebuploader/templates/search.html b/bh20simplewebuploader/templates/search.html index dbdca90..e69de29 100644 --- a/bh20simplewebuploader/templates/search.html +++ b/bh20simplewebuploader/templates/search.html @@ -1,10 +0,0 @@ -<div class="search"> - <input id="search-input" type="search" placeholder="FASTA uri" required> - <button class="button search-button" type="submit" onclick="search()"> - <span class="icon ion-search"> - <span class="sr-only">Search</span> - </span> - </button> - <span class="dropt" title="http://collections.lugli.arvadosapi.com/c=00fede2c6f52b053a14edca01cfa02b7+126/sequence.fasta">(example)<span style="width:500px;"></span></span> -</div> - diff --git a/bh20simplewebuploader/templates/status.html b/bh20simplewebuploader/templates/status.html index a1cf28f..e89437e 100644 --- a/bh20simplewebuploader/templates/status.html +++ b/bh20simplewebuploader/templates/status.html @@ -7,7 +7,8 @@ <h1>Sequence upload processing status</h1> - <div class="status"> + <div class="status"> + {{ table }} </div> diff --git a/bh20simplewebuploader/templates/success.html b/bh20simplewebuploader/templates/success.html index 9f0987c..c2302fa 100644 --- a/bh20simplewebuploader/templates/success.html +++ b/bh20simplewebuploader/templates/success.html @@ -9,7 +9,7 @@ <h1>Upload Successful</h1> <hr> <p> - Your files have been uploaded. They should soon appear as output of the <a href="/download">Public SARS-CoV-2 Sequence Resource</a>. + Your files have been uploaded. You can track their <a href="/status">QC status</a>, once validated they will be part of the <a href="/download">Public SARS-CoV-2 Sequence Resource</a>. </p> <p> The upload log was: diff --git a/bh20simplewebuploader/templates/validated.html b/bh20simplewebuploader/templates/validated.html new file mode 100644 index 0000000..cee94bd --- /dev/null +++ b/bh20simplewebuploader/templates/validated.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> + {% include 'header.html' %} + <body> + {% include 'banner.html' %} + {% include 'menu.html' %} + + <h1>Validated sequences</h1> + + <div class="status"> + {{ table }} + </div> + +{% include 'footer.html' %} + + </body> +</html> |