aboutsummaryrefslogtreecommitdiff
path: root/uploader
diff options
context:
space:
mode:
Diffstat (limited to 'uploader')
-rw-r--r--uploader/__init__.py2
-rw-r--r--uploader/files/views.py1
-rw-r--r--uploader/publications/__init__.py1
-rw-r--r--uploader/publications/models.py42
-rw-r--r--uploader/publications/views.py92
-rw-r--r--uploader/samples/views.py19
-rw-r--r--uploader/static/js/misc.js6
-rw-r--r--uploader/static/js/pubmed.js113
-rw-r--r--uploader/static/js/utils.js27
-rw-r--r--uploader/templates/base.html7
-rw-r--r--uploader/templates/phenotypes/add-phenotypes-base.html351
-rw-r--r--uploader/templates/publications/base.html12
-rw-r--r--uploader/templates/publications/create-publication.html191
-rw-r--r--uploader/templates/publications/index.html92
-rw-r--r--uploader/templates/publications/view-publication.html74
-rw-r--r--uploader/templates/samples/upload-failure.html2
16 files changed, 752 insertions, 280 deletions
diff --git a/uploader/__init__.py b/uploader/__init__.py
index 24c45a5..69d34f7 100644
--- a/uploader/__init__.py
+++ b/uploader/__init__.py
@@ -16,6 +16,7 @@ from . import session
from .base_routes import base
from .files.views import files
from .species import speciesbp
+from .publications import pubbp
from .oauth2.views import oauth2
from .expression_data import exprdatabp
from .errors import register_error_handlers
@@ -103,6 +104,7 @@ def create_app(config: dict = {}):
app.register_blueprint(files, url_prefix="/files")
app.register_blueprint(oauth2, url_prefix="/oauth2")
app.register_blueprint(speciesbp, url_prefix="/species")
+ app.register_blueprint(pubbp, url_prefix="/publications")
app.register_blueprint(background_jobs_bp, url_prefix="/background-jobs/")
register_error_handlers(app)
diff --git a/uploader/files/views.py b/uploader/files/views.py
index ddf5350..cebd325 100644
--- a/uploader/files/views.py
+++ b/uploader/files/views.py
@@ -56,6 +56,7 @@ def __merge_chunks__(targetfile: Path, chunkpaths: tuple[Path, ...]) -> Path:
"""Merge the chunks into a single file."""
with open(targetfile, "ab") as _target:
for chunkfile in chunkpaths:
+ app.logger.error("Merging chunk: %s", chunkfile)
with open(chunkfile, "rb") as _chunkdata:
_target.write(_chunkdata.read())
diff --git a/uploader/publications/__init__.py b/uploader/publications/__init__.py
index 57c0cbb..7efcabb 100644
--- a/uploader/publications/__init__.py
+++ b/uploader/publications/__init__.py
@@ -1 +1,2 @@
"""Package for handling publications."""
+from .views import pubbp
diff --git a/uploader/publications/models.py b/uploader/publications/models.py
index 3fc9542..8dd62f3 100644
--- a/uploader/publications/models.py
+++ b/uploader/publications/models.py
@@ -1,5 +1,6 @@
"""Module to handle persistence and retrieval of publication to/from MariaDB"""
import logging
+from typing import Iterable
from MySQLdb.cursors import DictCursor
@@ -41,15 +42,10 @@ def create_new_publications(
"%(pubmed_id)s, %(abstract)s, %(authors)s, %(title)s, "
"%(journal)s, %(volume)s, %(pages)s, %(month)s, %(year)s"
") "
- "ON DUPLICATE KEY UPDATE "
- "Abstract=VALUES(Abstract), Authors=VALUES(Authors), "
- "Title=VALUES(Title), Journal=VALUES(Journal), "
- "Volume=VALUES(Volume), Pages=VALUES(pages), "
- "Month=VALUES(Month), Year=VALUES(Year) "
"RETURNING *"),
publications)
return tuple({
- **row, "PublicationId": row["Id"]
+ **row, "publication_id": row["Id"]
} for row in cursor.fetchall())
return tuple()
@@ -71,3 +67,37 @@ def update_publications(conn: Connection , publications: tuple[dict, ...]) -> tu
return publications
return tuple()
return tuple()
+
+
+def fetch_publications(conn: Connection) -> Iterable[dict]:
+ """Fetch publications from the database."""
+ with conn.cursor(cursorclass=DictCursor) as cursor:
+ cursor.execute("SELECT * FROM Publication")
+ for row in cursor.fetchall():
+ yield dict(row)
+
+
+def fetch_publication_by_id(conn: Connection, publication_id: int) -> dict:
+ """Fetch a specific publication from the database."""
+ with conn.cursor(cursorclass=DictCursor) as cursor:
+ cursor.execute("SELECT * FROM Publication WHERE Id=%s",
+ (publication_id,))
+ return dict(cursor.fetchone())
+
+
+def fetch_publication_phenotypes(
+ conn: Connection, publication_id: int) -> Iterable[dict]:
+ """Fetch all phenotypes linked to this publication."""
+ with conn.cursor(cursorclass=DictCursor) as cursor:
+ cursor.execute(
+ "SELECT pxr.Id AS xref_id, pxr.PublicationId, phe.* "
+ "FROM PublishXRef AS pxr INNER JOIN Phenotype AS phe "
+ "ON pxr.PhenotypeId=phe.Id "
+ "WHERE pxr.PublicationId=%s",
+ (publication_id,))
+ while True:
+ row = cursor.fetchone()
+ if row:
+ yield row
+ else:
+ break
diff --git a/uploader/publications/views.py b/uploader/publications/views.py
new file mode 100644
index 0000000..ebb8740
--- /dev/null
+++ b/uploader/publications/views.py
@@ -0,0 +1,92 @@
+"""Endpoints for publications"""
+import json
+
+from gn_libs.mysqldb import database_connection
+from flask import (
+ flash,
+ request,
+ url_for,
+ redirect,
+ Blueprint,
+ render_template,
+ current_app as app)
+
+from uploader.authorisation import require_login
+
+from .models import (
+ fetch_publications,
+ fetch_publication_by_id,
+ create_new_publications,
+ fetch_publication_phenotypes)
+
+from gn_libs.debug import __pk__
+
+pubbp = Blueprint("publications", __name__)
+
+
+@pubbp.route("/", methods=["GET"])
+@require_login
+def index():
+ """Index page for publications."""
+ with database_connection(app.config["SQL_URI"]) as conn:
+ return render_template("publications/index.html")
+
+
+@pubbp.route("/list", methods=["GET"])
+@require_login
+def list_publications():
+ with database_connection(app.config["SQL_URI"]) as conn:
+ return json.dumps({
+ "publications": tuple({
+ **row, "index": idx
+ } for idx,row in enumerate(
+ fetch_publications(conn), start=1)),
+ "status": "success"
+ })
+
+
+@pubbp.route("/view/<int:publication_id>", methods=["GET"])
+@require_login
+def view_publication(publication_id: int):
+ """View more details on a particular publication."""
+ with database_connection(app.config["SQL_URI"]) as conn:
+ return render_template(
+ "publications/view-publication.html",
+ publication=fetch_publication_by_id(conn, publication_id),
+ linked_phenotypes=tuple(fetch_publication_phenotypes(
+ conn, publication_id)))
+
+
+@pubbp.route("/create", methods=["GET", "POST"])
+@require_login
+def create_publication():
+ """Create a new publication."""
+ if(request.method == "GET"):
+ return render_template("publications/create-publication.html")
+ form = request.form
+ authors = form.get("publication-authors").encode("utf8")
+ if authors is None or authors == "":
+ flash("The publication's author(s) MUST be provided!", "alert alert-danger")
+ return redirect(url_for("publications.create", **request.args))
+
+ with database_connection(app.config["SQL_URI"]) as conn:
+ publications = create_new_publications(conn, ({
+ "pubmed_id": form.get("pubmed-id"),
+ "abstract": form.get("publication-abstract").encode("utf8") or None,
+ "authors": authors,
+ "title": form.get("publication-title").encode("utf8") or None,
+ "journal": form.get("publication-journal").encode("utf8") or None,
+ "volume": form.get("publication-volume").encode("utf8") or None,
+ "pages": form.get("publication-pages").encode("utf8") or None,
+ "month": (form.get("publication-month") or "").encode("utf8").capitalize() or None,
+ "year": form.get("publication-year").encode("utf8") or None
+ },))
+ flash("New publication created!", "alert alert-success")
+ return redirect(url_for(
+ request.args.get("return_to") or "publications.view_publication",
+ publication_id=publications[0]["publication_id"],
+ **request.args))
+
+ flash("Publication creation failed!", "alert alert-danger")
+ app.logger.debug("Failed to create the new publication.", exc_info=True)
+ return redirect(url_for("publications.create_publication"))
diff --git a/uploader/samples/views.py b/uploader/samples/views.py
index 27e5d3c..c0adb88 100644
--- a/uploader/samples/views.py
+++ b/uploader/samples/views.py
@@ -221,7 +221,10 @@ def upload_status(species: dict, population: dict, job_id: uuid.UUID, **kwargs):
if status == "error":
return redirect(url_for(
- "species.populations.samples.upload_failure", job_id=job_id))
+ "species.populations.samples.upload_failure",
+ species_id=species["SpeciesId"],
+ population_id=population["Id"],
+ job_id=job_id))
error_filename = Path(jobs.error_filename(
job_id, f"{app.config['UPLOAD_FOLDER']}/job_errors"))
@@ -241,9 +244,14 @@ def upload_status(species: dict, population: dict, job_id: uuid.UUID, **kwargs):
species=species,
population=population), 400
-@samplesbp.route("/upload/failure/<uuid:job_id>", methods=["GET"])
+
+@samplesbp.route("<int:species_id>/populations/<int:population_id>/"
+ "upload-samples/failure/<uuid:job_id>",
+ methods=["GET"])
@require_login
-def upload_failure(job_id: uuid.UUID):
+@with_population(species_redirect_uri="species.populations.samples.index",
+ redirect_uri="species.populations.samples.select_population")
+def upload_failure(species: dict, population: dict, job_id: uuid.UUID, **kwargs):
"""Display the errors of the samples upload failure."""
job = with_redis_connection(lambda rconn: jobs.job(
rconn, jobs.jobsnamespace(), job_id))
@@ -257,4 +265,7 @@ def upload_failure(job_id: uuid.UUID):
if stat.st_size > 0:
return render_template("worker_failure.html", job_id=job_id)
- return render_template("samples/upload-failure.html", job=job)
+ return render_template("samples/upload-failure.html",
+ species=species,
+ population=population,
+ job=job)
diff --git a/uploader/static/js/misc.js b/uploader/static/js/misc.js
deleted file mode 100644
index cf7b39e..0000000
--- a/uploader/static/js/misc.js
+++ /dev/null
@@ -1,6 +0,0 @@
-"Miscellaneous functions and event-handlers"
-
-$(".not-implemented").click((event) => {
- event.preventDefault();
- alert("This feature is not implemented yet. Please bear with us.");
-});
diff --git a/uploader/static/js/pubmed.js b/uploader/static/js/pubmed.js
new file mode 100644
index 0000000..9afd4c3
--- /dev/null
+++ b/uploader/static/js/pubmed.js
@@ -0,0 +1,113 @@
+var extract_details = (pubmed_id, details) => {
+ var months = {
+ "jan": "January",
+ "feb": "February",
+ "mar": "March",
+ "apr": "April",
+ "may": "May",
+ "jun": "June",
+ "jul": "July",
+ "aug": "August",
+ "sep": "September",
+ "oct": "October",
+ "nov": "November",
+ "dec": "December"
+ };
+ var _date = details[pubmed_id].pubdate.split(" ");
+ return {
+ "authors": details[pubmed_id].authors.map((authobj) => {
+ return authobj.name;
+ }),
+ "title": details[pubmed_id].title,
+ "journal": details[pubmed_id].fulljournalname,
+ "volume": details[pubmed_id].volume,
+ "pages": details[pubmed_id].pages,
+ "month": _date.length > 1 ? months[_date[1].toLowerCase()] : "jan",
+ "year": _date[0],
+ };
+};
+
+var update_publication_details = (details) => {
+ Object.entries(details).forEach((entry) => {;
+ switch(entry[0]) {
+ case "authors":
+ $("#txt-publication-authors").val(entry[1].join(", "));
+ break;
+ case "month":
+ $("#select-publication-month")
+ .children("option")
+ .each((index, child) => {
+ console.debug(entry[1].toLowerCase());
+ child.selected = child.value == entry[1].toLowerCase();
+ });
+ default:
+ $("#txt-publication-" + entry[0]).val(entry[1]);
+ break;
+ }
+ });
+};
+
+var fetch_publication_abstract = (pubmed_id, pub_details) => {
+ $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi",
+ {
+ "method": "GET",
+ "data": {
+ "db": "pubmed",
+ "id": pubmed_id,
+ "rettype": "abstract",
+ "retmode": "xml"
+ },
+ "success": (data, textStatus, jqXHR) => {
+ update_publication_details({
+ ...pub_details,
+ ...{
+ "abstract": Array.from(data
+ .getElementsByTagName(
+ "Abstract")[0]
+ .children)
+ .map((elt) => {return elt.textContent.trim();})
+ .join("\r\n")
+ }});
+ },
+ "error": (jqXHR, textStatus, errorThrown) => {},
+ "complete": (jqXHR, textStatus) => {},
+ "dataType": "xml"
+ });
+};
+
+var fetch_publication_details = (pubmed_id, complete_thunks) => {
+ error_display = $("#search-pubmed-id-error");
+ error_display.text("");
+ add_class(error_display, "visually-hidden");
+ $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi",
+ {
+ "method": "GET",
+ "data": {"db": "pubmed", "id": pubmed_id, "format": "json"},
+ "success": (data, textStatus, jqXHR) => {
+ // process and update publication details
+ hasError = (
+ Object.hasOwn(data, "error") ||
+ Object.hasOwn(data.result[pubmed_id], "error"));
+ if(hasError) {
+ error_display.text(
+ "There was an error fetching a publication with " +
+ "the given PubMed ID! The error received " +
+ "was: '" + (
+ data.error ||
+ data.result[pubmed_id].error) +
+ "'. Please check ID you provided and try " +
+ "again.");
+ remove_class(error_display, "visually-hidden");
+ } else {
+ fetch_publication_abstract(
+ pubmed_id,
+ extract_details(pubmed_id, data.result));
+ }
+ },
+ "error": (jqXHR, textStatus, errorThrown) => {},
+ "complete": () => {
+ complete_thunks.forEach((thunk) => {thunk()});
+ },
+ "dataType": "json"
+ });
+};
diff --git a/uploader/static/js/utils.js b/uploader/static/js/utils.js
index 045dd47..1b31661 100644
--- a/uploader/static/js/utils.js
+++ b/uploader/static/js/utils.js
@@ -8,3 +8,30 @@ function trigger_change_event(element) {
evt = new Event("change");
element.dispatchEvent(evt);
}
+
+
+var remove_class = (element, classvalue) => {
+ new_classes = (element.attr("class") || "").split(" ").map((val) => {
+ return val.trim();
+ }).filter((val) => {
+ return ((val !== classvalue) &&
+ (val !== ""))
+ }).join(" ");
+
+ if(new_classes === "") {
+ element.removeAttr("class");
+ } else {
+ element.attr("class", new_classes);
+ }
+};
+
+
+var add_class = (element, classvalue) => {
+ remove_class(element, classvalue);
+ element.attr("class", (element.attr("class") || "") + " " + classvalue);
+};
+
+$(".not-implemented").click((event) => {
+ event.preventDefault();
+ alert("This feature is not implemented yet. Please bear with us.");
+});
diff --git a/uploader/templates/base.html b/uploader/templates/base.html
index 09e6470..3c0d0d4 100644
--- a/uploader/templates/base.html
+++ b/uploader/templates/base.html
@@ -32,7 +32,7 @@
<a href="{{url_for('oauth2.logout')}}"
title="Log out of the system">
<span class="glyphicon glyphicon-user"></span>
- Sign Out</a>
+ {{user_email()}} Sign Out</a>
{%else%}
<a href="{{authserver_authorise_uri()}}"
title="Log in to the system">Sign In</a>
@@ -46,6 +46,9 @@
<ul class="nav flex-column">
<li {%if activemenu=="home"%}class="activemenu"{%endif%}>
<a href="/" >Home</a></li>
+ <li {%if activemenu=="publications"%}class="activemenu"{%endif%}>
+ <a href="{{url_for('publications.index')}}"
+ title="View and manage publications.">Publications</a></li>
<li {%if activemenu=="species"%}class="activemenu"{%endif%}>
<a href="{{url_for('species.list_species')}}"
title="View and manage species information.">Species</a></li>
@@ -151,7 +154,7 @@
<!--
local dependencies
-->
- <script type="text/javascript" src="/static/js/misc.js"></script>
+ <script type="text/javascript" src="/static/js/utils.js"></script>
<script type="text/javascript" src="/static/js/datatables.js"></script>
{%block javascript%}{%endblock%}
</body>
diff --git a/uploader/templates/phenotypes/add-phenotypes-base.html b/uploader/templates/phenotypes/add-phenotypes-base.html
index a2d9484..a7aaeb0 100644
--- a/uploader/templates/phenotypes/add-phenotypes-base.html
+++ b/uploader/templates/phenotypes/add-phenotypes-base.html
@@ -42,110 +42,30 @@
{%block frm_add_phenotypes_elements%}{%endblock%}
- <div class="checkbox">
- <label>
- <input id="chk-published" type="checkbox" name="published?" />
- These phenotypes are published</label>
- </div>
-
- <fieldset id="fldset-publication-info" class="visually-hidden">
+ <fieldset id="fldset-publication-info">
<legend>Publication Information</legend>
- <div class="form-group">
- <label for="txt-pubmed-id" class="form-label">Pubmed ID</label>
- <div class="input-group">
- <input id="txt-pubmed-id" name="pubmed-id" type="text"
- class="form-control" />
- <span class="input-group-btn">
- <button id="btn-search-pubmed-id" class="btn btn-info">Search</button>
- </span>
- </div>
- <span id="search-pubmed-id-error"
- class="form-text text-muted text-danger visually-hidden">
- </span><br />
- <span class="form-text text-muted">
- Enter your publication's PubMed ID above and click "Search" to search
- for some (or all) of the publication details requested below.
- </span>
- </div>
-
- <div class="form-group">
- <label for="txt-publication-authors" class="form-label">Authors</label>
- <input id="txt-publication-authors" name="publication-authors"
- type="text" class="form-control" />
- <span class="form-text text-muted">
- Enter the authors in the following format &hellip;</span>
- </div>
-
- <div class="form-group">
- <label for="txt-publication-title" class="form-label">
- Publication Title</label>
- <input id="txt-publication-title" name="publication-title" type="text"
- class="form-control" />
- <span class="form-text text-muted">
- Enter your publication's title.</span>
- </div>
-
- <div class="form-group">
- <label for="txt-publication-abstract" class="form-label">
- Publication Abstract</label>
- <textarea id="txt-publication-abstract" name="publication-abstract"
- class="form-control" rows="10"></textarea>
- <span class="form-text text-muted">
- Enter the abstract for your publication.</span>
- </div>
-
- <div class="form-group">
- <label for="txt-publication-journal" class="form-label">Journal</label>
- <input id="txt-publication-journal" name="journal" type="text"
- class="form-control" />
- <span class="form-text text-muted">
- Enter the name of the journal where your work was published.</span>
- </div>
-
- <div class="form-group">
- <label for="txt-publication-volume" class="form-label">Volume</label>
- <input id="txt-publication-volume" name="publication-volume" type="text"
- class="form-control" />
- <span class="form-text text-muted">
- Enter the volume in the following format &hellip;</span>
- </div>
-
- <div class="form-group">
- <label for="txt-publication-pages" class="form-label">Pages</label>
- <input id="txt-publication-pages" name="publication-pages" type="text"
- class="form-control" />
- <span class="form-text text-muted">
- Enter the journal volume where your work was published.</span>
- </div>
-
- <div class="form-group">
- <label for="select-publication-month" class="form-label">
- Publication Month</label>
- <select id="select-publication-month" name="publication-month"
- class="form-control">
- {%for month in monthnames%}
- <option value="{{month | lower}}"
- {%if current_month | lower == month | lower%}
- selected="selected"
- {%endif%}>{{month | capitalize}}</option>
- {%endfor%}
- </select>
- <span class="form-text text-muted">
- Select the month when the work was published.
- <span class="text-danger">
- This cannot be before, say 1600 and cannot be in the future!</span></span>
- </div>
-
- <div class="form-group">
- <label for="txt-publication-year" class="form-label">Publication Year</label>
- <input id="txt-publication-year" name="publication-year" type="text"
- class="form-control" value="{{current_year}}" />
- <span class="form-text text-muted">
- Enter the year your work was published.
- <span class="text-danger">
- This cannot be before, say 1600 and cannot be in the future!</span>
- </span>
- </div>
+ <input type="hidden" name="publication-id" id="txt-publication-id" />
+ <span class="form-text text-muted">
+ Select a publication for your data. <br />
+ Can't find a publication you can use? Go ahead and
+ <a href="{{url_for(
+ 'publications.create_publication',
+ return_to='species.populations.phenotypes.add_phenotypes',
+ species_id=species.SpeciesId,
+ population_id=population.Id,
+ dataset_id=dataset.Id)}}">create a new publication</a>.</span>
+ <table id="tbl-select-publication" class="table compact stripe">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>PubMed ID</th>
+ <th>Title</th>
+ <th>Authors</th>
+ </tr>
+ </thead>
+
+ <tbody></tbody>
+ </table>
</fieldset>
<div class="form-group">
@@ -165,165 +85,74 @@
{%block javascript%}
<script type="text/javascript">
- var remove_class = (element, classvalue) => {
- new_classes = (element.attr("class") || "").split(" ").map((val) => {
- return val.trim();
- }).filter((val) => {
- return ((val !== classvalue) &&
- (val !== ""))
- }).join(" ");
-
- if(new_classes === "") {
- element.removeAttr("class");
- } else {
- element.attr("class", new_classes);
- }
- };
-
- var add_class = (element, classvalue) => {
- remove_class(element, classvalue);
- element.attr("class", (element.attr("class") || "") + " " + classvalue);
- };
-
- $("#chk-published").on("click", (event) => {
- pub_details = $("#fldset-publication-info")
- if(event.target.checked) {
- // display the publication details
- remove_class(pub_details, "visually-hidden");
- } else {
- // hide the publication details
- add_class(pub_details, "visually-hidden");
- }
- });
-
- var extract_details = (pubmed_id, details) => {
- var months = {
- "jan": "January",
- "feb": "February",
- "mar": "March",
- "apr": "April",
- "may": "May",
- "jun": "June",
- "jul": "July",
- "aug": "August",
- "sep": "September",
- "oct": "October",
- "nov": "November",
- "dec": "December"
- };
- var _date = details[pubmed_id].pubdate.split(" ");
- return {
- "authors": details[pubmed_id].authors.map((authobj) => {
- return authobj.name;
- }),
- "title": details[pubmed_id].title,
- "journal": details[pubmed_id].fulljournalname,
- "volume": details[pubmed_id].volume,
- "pages": details[pubmed_id].pages,
- "month": _date.length > 1 ? months[_date[1].toLowerCase()] : "jan",
- "year": _date[0],
- };
- };
-
- var update_publication_details = (details) => {
- Object.entries(details).forEach((entry) => {;
- switch(entry[0]) {
- case "authors":
- $("#txt-publication-authors").val(entry[1].join(", "));
- break;
- case "month":
- $("#select-publication-month")
- .children("option")
- .each((index, child) => {
- child.selected = child.value == entry[1].toLowerCase();
- });
- default:
- $("#txt-publication-" + entry[0]).val(entry[1]);
- break;
- }
+ $(function() {
+ var publicationsDataTable = buildDataTable(
+ "#tbl-select-publication",
+ [],
+ [
+ {data: "index"},
+ {
+ data: (pub) => {
+ if(pub.PubMed_ID) {
+ return `<a href="https://pubmed.ncbi.nlm.nih.gov/` +
+ `${pub.PubMed_ID}/" target="_blank" ` +
+ `title="Link to publication on NCBI.">` +
+ `${pub.PubMed_ID}</a>`;
+ }
+ return "";
+ }
+ },
+ {
+ data: (pub) => {
+ var title = "⸻";
+ if(pub.Title) {
+ title = pub.Title
+ }
+ return `<a href="/publications/view/${pub.Id}" ` +
+ `target="_blank" ` +
+ `title="Link to view publication details">` +
+ `${title}</a>`;
+ }
+ },
+ {
+ data: (pub) => {
+ authors = pub.Authors.split(",").map(
+ (item) => {return item.trim();});
+ if(authors.length > 1) {
+ return authors[0] + ", et. al.";
+ }
+ return authors[0];
+ }
+ }
+ ],
+ {
+ ajax: {
+ url: "/publications/list",
+ dataSrc: "publications"
+ },
+ select: "single",
+ scrollY: 700,
+ paging: false,
+ deferRender: true,
+ layout: {
+ topStart: "info",
+ topEnd: "search"
+ }
+ });
+ publicationsDataTable.on("select", (event, datatable, type, indexes) => {
+ indexes.forEach((element, index, thearray) => {
+ let row = datatable.row(element).node();
+ console.debug(datatable.row(element).data());
+ $("#frm-add-phenotypes #txt-publication-id").val(
+ datatable.row(element).data().Id);
+ });
+ });
+ publicationsDataTable.on("deselect", (event, datatable, type, indexes) => {
+ indexes.forEach((element, index, thearray) => {
+ let row = datatable.row(element).node();
+ $("#frm-add-phenotypes #txt-publication-id").val(null);
+ });
});
- };
-
- var fetch_publication_abstract = (pubmed_id, pub_details) => {
- $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi",
- {
- "method": "GET",
- "data": {
- "db": "pubmed",
- "id": pubmed_id,
- "rettype": "abstract",
- "retmode": "xml"
- },
- "success": (data, textStatus, jqXHR) => {
- update_publication_details({
- ...pub_details,
- ...{
- "abstract": Array.from(data
- .getElementsByTagName(
- "Abstract")[0]
- .children)
- .map((elt) => {return elt.textContent.trim();})
- .join("\r\n")
- }});
- },
- "error": (jqXHR, textStatus, errorThrown) => {},
- "complete": (jqXHR, textStatus) => {},
- "dataType": "xml"
- });
- };
-
- var fetch_publication_details = (pubmed_id, complete_thunks) => {
- error_display = $("#search-pubmed-id-error");
- error_display.text("");
- add_class(error_display, "visually-hidden");
- $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi",
- {
- "method": "GET",
- "data": {"db": "pubmed", "id": pubmed_id, "format": "json"},
- "success": (data, textStatus, jqXHR) => {
- // process and update publication details
- hasError = (
- Object.hasOwn(data, "error") ||
- Object.hasOwn(data.result[pubmed_id], "error"));
- if(hasError) {
- error_display.text(
- "There was an error fetching a publication with " +
- "the given PubMed ID! The error received " +
- "was: '" + (
- data.error ||
- data.result[pubmed_id].error) +
- "'. Please check ID you provided and try " +
- "again.");
- remove_class(error_display, "visually-hidden");
- } else {
- fetch_publication_abstract(
- pubmed_id,
- extract_details(pubmed_id, data.result));
- }
- },
- "error": (jqXHR, textStatus, errorThrown) => {},
- "complete": () => {
- complete_thunks.forEach((thunk) => {thunk()});
- },
- "dataType": "json"
- });
- };
-
- $("#btn-search-pubmed-id").on("click", (event) => {
- event.preventDefault();
- var search_button = event.target;
- var pubmed_id = $("#txt-pubmed-id").val().trim();
- remove_class($("#txt-pubmed-id").parent(), "has-error");
- if(pubmed_id == "") {
- add_class($("#txt-pubmed-id").parent(), "has-error");
- return false;
- }
-
- search_button.disabled = true;
- // Fetch publication details
- fetch_publication_details(pubmed_id,
- [() => {search_button.disabled = false;}]);
- return false;
});
</script>
diff --git a/uploader/templates/publications/base.html b/uploader/templates/publications/base.html
new file mode 100644
index 0000000..db80bfa
--- /dev/null
+++ b/uploader/templates/publications/base.html
@@ -0,0 +1,12 @@
+{%extends "base.html"%}
+
+{%block lvl1_breadcrumbs%}
+<li {%if activelink=="publications"%}
+ class="breadcrumb-item active"
+ {%else%}
+ class="breadcrumb-item"
+ {%endif%}>
+ <a href="{{url_for('publications.index')}}">Publications</a>
+</li>
+{%block lvl2_breadcrumbs%}{%endblock%}
+{%endblock%}
diff --git a/uploader/templates/publications/create-publication.html b/uploader/templates/publications/create-publication.html
new file mode 100644
index 0000000..3f828a9
--- /dev/null
+++ b/uploader/templates/publications/create-publication.html
@@ -0,0 +1,191 @@
+{%extends "publications/base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}View Publication{%endblock%}
+
+{%block pagetitle%}View Publication{%endblock%}
+
+
+{%block contents%}
+{{flash_all_messages()}}
+
+<div class="row">
+ <form id="frm-create-publication"
+ method="POST"
+ action="{{url_for('publications.create_publication', **request.args)}}"
+ class="form-horizontal">
+
+ <div class="row mb-3">
+ <label for="txt-pubmed-id" class="col-sm-2 col-form-label">
+ PubMed ID</label>
+ <div class="col-sm-10">
+ <div class="input-group">
+ <input type="text"
+ id="txt-pubmed-id"
+ name="pubmed-id"
+ class="form-control"/>
+ <div class="input-group-text">
+ <button class="btn btn-outline-primary"
+ id="btn-search-pubmed-id">search</button>
+ </div>
+ </div>
+ <span id="search-pubmed-id-error"
+ class="form-text text-muted text-danger visually-hidden">
+ </span>
+ <span class="form-text text-muted">This is the publication's ID on
+ <a href="https://pubmed.ncbi.nlm.nih.gov/"
+ title="Link to NCBI's PubMed service">NCBI's Pubmed Service</a>
+ </span>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label for="txt-publication-title" class="col-sm-2 col-form-label">
+ Title</label>
+ <div class="col-sm-10">
+ <input type="text"
+ id="txt-publication-title"
+ name="publication-title"
+ class="form-control" />
+ <span class="form-text text-muted">Provide the publication's title here.</span>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label for="txt-publication-authors" class="col-sm-2 col-form-label">
+ Authors</label>
+ <div class="col-sm-10">
+ <input type="text"
+ id="txt-publication-authors"
+ name="publication-authors"
+ required="required"
+ class="form-control" />
+ <span class="form-text text-muted">
+ A publication <strong>MUST</strong> have an author. You <em>must</em>
+ provide a value for the authors field.
+ </span>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label for="txt-publication-journal" class="col-sm-2 col-form-label">
+ Journal</label>
+ <div class="col-sm-10">
+ <input type="text"
+ id="txt-publication-journal"
+ name="publication-journal"
+ class="form-control" />
+ <span class="form-text text-muted">Provide the name journal where the
+ publication was done, here.</span>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label for="select-publication-month"
+ class="col-sm-2 col-form-label">
+ Month</label>
+ <div class="col-sm-4">
+ <select class="form-control"
+ id="select-publication-month"
+ name="publication-month">
+ <option value="">Select a month</option>
+ <option value="january">January</option>
+ <option value="february">February</option>
+ <option value="march">March</option>
+ <option value="april">April</option>
+ <option value="may">May</option>
+ <option value="june">June</option>
+ <option value="july">July</option>
+ <option value="august">August</option>
+ <option value="september">September</option>
+ <option value="october">October</option>
+ <option value="november">November</option>
+ <option value="december">December</option>
+ </select>
+ <span class="form-text text-muted">Month of publication</span>
+ </div>
+
+ <label for="txt-publication-year"
+ class="col-sm-2 col-form-label">
+ Year</label>
+ <div class="col-sm-4">
+ <input type="number"
+ id="txt-publication-year"
+ name="publication-year"
+ class="form-control"
+ min="1960" />
+ <span class="form-text text-muted">Year of publication</span>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label for="txt-publication-volume"
+ class="col-sm-2 col-form-label">
+ Volume</label>
+ <div class="col-sm-4">
+ <input type="text"
+ id="txt-publication-volume"
+ name="publication-volume"
+ class="form-control">
+ <span class="form-text text-muted">Journal volume</span>
+ </div>
+
+ <label for="txt-publication-pages"
+ class="col-sm-2 col-form-label">
+ Pages</label>
+ <div class="col-sm-4">
+ <input type="text"
+ id="txt-publication-pages"
+ name="publication-pages"
+ class="form-control" />
+ <span class="form-text text-muted">Journal pages for the publication</span>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <label for="txt-abstract" class="col-sm-2 col-form-label">Abstract</label>
+ <div class="col-sm-10">
+ <textarea id="txt-publication-abstract"
+ name="publication-abstract"
+ class="form-control"
+ rows="7"></textarea>
+ </div>
+ </div>
+
+ <div class="row mb-3">
+ <div class="col-sm-2"></div>
+ <div class="col-sm-8">
+ <input type="submit" class="btn btn-primary" value="Add" />
+ <input type="reset" class="btn btn-danger" />
+ </div>
+ </div>
+
+</form>
+</div>
+
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript" src="/static/js/pubmed.js"></script>
+<script type="text/javascript">
+ $(function() {
+ $("#btn-search-pubmed-id").on("click", (event) => {
+ event.preventDefault();
+ var search_button = event.target;
+ var pubmed_id = $("#txt-pubmed-id").val().trim();
+ remove_class($("#txt-pubmed-id").parent(), "has-error");
+ if(pubmed_id == "") {
+ add_class($("#txt-pubmed-id").parent(), "has-error");
+ return false;
+ }
+
+ search_button.disabled = true;
+ // Fetch publication details
+ fetch_publication_details(pubmed_id,
+ [() => {search_button.disabled = false;}]);
+ return false;
+ });
+ });
+</script>
+{%endblock%}
diff --git a/uploader/templates/publications/index.html b/uploader/templates/publications/index.html
new file mode 100644
index 0000000..f846d54
--- /dev/null
+++ b/uploader/templates/publications/index.html
@@ -0,0 +1,92 @@
+{%extends "publications/base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Publications{%endblock%}
+
+{%block pagetitle%}Publications{%endblock%}
+
+
+{%block contents%}
+{{flash_all_messages()}}
+
+<div class="row" style="padding-bottom: 1em;">
+ <a href="{{url_for('publications.create_publication')}}"
+ class="btn btn-primary">
+ add new publication</a>
+</div>
+
+<div class="row">
+ <table id="tbl-list-publications" class="table compact stripe">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>PubMed ID</th>
+ <th>Title</th>
+ <th>Authors</th>
+ </tr>
+ </thead>
+
+ <tbody></tbody>
+ </table>
+</div>
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript">
+ $(function() {
+ var publicationsDataTable = buildDataTable(
+ "#tbl-list-publications",
+ [],
+ [
+ {data: "index"},
+ {
+ data: (pub) => {
+ if(pub.PubMed_ID) {
+ return `<a href="https://pubmed.ncbi.nlm.nih.gov/` +
+ `${pub.PubMed_ID}/" target="_blank" ` +
+ `title="Link to publication on NCBI.">` +
+ `${pub.PubMed_ID}</a>`;
+ }
+ return "";
+ }
+ },
+ {
+ data: (pub) => {
+ var title = "⸻";
+ if(pub.Title) {
+ title = pub.Title
+ }
+ return `<a href="/publications/view/${pub.Id}" ` +
+ `target="_blank" ` +
+ `title="Link to view publication details">` +
+ `${title}</a>`;
+ }
+ },
+ {
+ data: (pub) => {
+ authors = pub.Authors.split(",").map(
+ (item) => {return item.trim();});
+ if(authors.length > 1) {
+ return authors[0] + ", et. al.";
+ }
+ return authors[0];
+ }
+ }
+ ],
+ {
+ ajax: {
+ url: "/publications/list",
+ dataSrc: "publications"
+ },
+ scrollY: 700,
+ paging: false,
+ deferRender: true,
+ layout: {
+ topStart: "info",
+ topEnd: "search"
+ }
+ });
+ });
+</script>
+{%endblock%}
diff --git a/uploader/templates/publications/view-publication.html b/uploader/templates/publications/view-publication.html
new file mode 100644
index 0000000..5b93434
--- /dev/null
+++ b/uploader/templates/publications/view-publication.html
@@ -0,0 +1,74 @@
+{%extends "publications/base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}View Publication{%endblock%}
+
+{%block pagetitle%}View Publication{%endblock%}
+
+
+{%block contents%}
+{{flash_all_messages()}}
+
+<div class="row">
+ <table class="table">
+ <tr>
+ <th>PubMed</th>
+ <td>
+ {%if publication.PubMed_ID%}
+ <a href="https://pubmed.ncbi.nlm.nih.gov/{{publication.PubMed_ID}}/"
+ target="_blank">{{publication.PubMed_ID}}</a>
+ {%else%}
+ —
+ {%endif%}
+ </td>
+ </tr>
+ <tr>
+ <th>Title</th>
+ <td>{{publication.Title or "—"}}</td>
+ </tr>
+ <tr>
+ <th>Authors</th>
+ <td>{{publication.Authors or "—"}}</td>
+ </tr>
+ <tr>
+ <th>Journal</th>
+ <td>{{publication.Journal or "—"}}</td>
+ </tr>
+ <tr>
+ <th>Published</th>
+ <td>{{publication.Month or ""}} {{publication.Year or "—"}}</td>
+ </tr>
+ <tr>
+ <th>Volume</th>
+ <td>{{publication.Volume or "—"}}</td>
+ </tr>
+ <tr>
+ <th>Pages</th>
+ <td>{{publication.Pages or "—"}}</td>
+ </tr>
+ <tr>
+ <th>Abstract</th>
+ <td>{{publication.Abstract or "—"}}</td>
+ </tr>
+ </table>
+</div>
+
+<div class="row">
+ <form id="frm-edit-delete-publication" method="POST" action="#">
+ <input type="hidden" name="publication_id" value="{{publication.Id}}" />
+ <div class="form-group">
+ <input type="submit" value="edit" class="btn btn-primary not-implemented" />
+ {%if linked_phenotypes | length == 0%}
+ <input type="submit" value="delete" class="btn btn-danger not-implemented" />
+ {%endif%}
+ </div>
+ </form>
+</div>
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript">
+ $(function() {});
+</script>
+{%endblock%}
diff --git a/uploader/templates/samples/upload-failure.html b/uploader/templates/samples/upload-failure.html
index 458ab55..2cf8053 100644
--- a/uploader/templates/samples/upload-failure.html
+++ b/uploader/templates/samples/upload-failure.html
@@ -15,7 +15,7 @@
<h3>Debugging Information</h3>
<ul>
- <li><strong>job id</strong>: {{job.job_id}}</li>
+ <li><strong>job id</strong>: {{job.jobid}}</li>
<li><strong>status</strong>: {{job.status}}</li>
<li><strong>job type</strong>: {{job["job-type"]}}</li>
</ul>