From fbaf6e2ee458a3e6cf09a9678264ba74d5046752 Mon Sep 17 00:00:00 2001
From: Frederick Muriuki Muriithi
Date: Fri, 7 Apr 2023 15:06:05 +0300
Subject: Move JS to external file. Generalise functions.
Move the javascript to an external file to ease development.
Generalise some functions to make it easier to reuse the code down the line.
---
.../static/new/javascript/auth/search_genotypes.js | 207 +++++++++++++++++++++
.../templates/oauth2/data-list-genotype.html | 102 +---------
2 files changed, 210 insertions(+), 99 deletions(-)
create mode 100644 wqflask/wqflask/static/new/javascript/auth/search_genotypes.js
diff --git a/wqflask/wqflask/static/new/javascript/auth/search_genotypes.js b/wqflask/wqflask/static/new/javascript/auth/search_genotypes.js
new file mode 100644
index 00000000..39999e76
--- /dev/null
+++ b/wqflask/wqflask/static/new/javascript/auth/search_genotypes.js
@@ -0,0 +1,207 @@
+/**
+ * Build a checkbox: For internal use only
+ * @param {Genotype Dataset object} A genotype dataset object
+ * @param {String} A string to initialise the checkbox
+ */
+function __build_checkbox__(dataset, checkbox_str) {
+ cell = $("
")
+ cell = $('');
+ cell.append(
+ $(''));
+ cell.append(" ");
+ cell.append("No genotype datasets remaining.");
+ row.append(cell);
+ $(table_id + " tbody").append(row);
+ }
+ table_data.forEach(function(dataset) {
+ row = $("")
+ row.append(checkbox_function(dataset));
+ row.append(table_cell(dataset.InbredSetName));
+ row.append(table_cell(dataset.dataset_name));
+ row.append(table_cell(dataset.dataset_fullname));
+ row.append(table_cell(dataset.dataset_shortname));
+ $(table_id + " tbody").append(row);
+ });
+}
+
+function in_array(dataset, datasets) {
+ found = datasets.filter(function(dst) {
+ return (dst.SpeciesId == dataset.SpeciesId &&
+ dst.InbredSetId == dataset.InbredSetId &&
+ dst.GenoFreezeId == dataset.GenoFreezeId);
+ });
+ return found.length > 0;
+}
+
+function remove_from_table_data(dataset, table_data_source) {
+ let table_id = table_data_source.table_id.selector;
+ let data_attr_name = table_data_source.data_attribute_name;
+ without_dataset = JSON.parse($(table_id).attr(data_attr_name)).filter(
+ function(dst) {
+ return !(dst.SpeciesId == dataset.SpeciesId &&
+ dst.InbredSetId == dataset.InbredSetId &&
+ dst.GenoFreezeId == dataset.GenoFreezeId);
+ });
+ $(table_id).attr(data_attr_name, JSON.stringify(without_dataset));
+}
+
+function add_to_table_data(dataset, table_data_source) {
+ let table_id = table_data_source.table_id.selector;
+ let data_attr_name = table_data_source.data_attribute_name;
+ table_data = JSON.parse($(table_id).attr(data_attr_name));
+ if(!in_array(dataset, table_data)) {
+ table_data.push(dataset);
+ }
+ $(table_id).attr(data_attr_name, JSON.stringify(Array.from(table_data)));
+}
+
+function toggle_link_button() {
+ num_groups = $("#frm-link-genotypes select option").length - 1;
+ num_selected = JSON.parse(
+ $("#tbl-link-genotypes").attr("data-selected-datasets")).length;
+ if(num_groups > 0 && num_selected > 0) {
+ $("#frm-link-genotypes input[type='submit']").prop("disabled", false);
+ } else {
+ $("#frm-link-genotypes input[type='submit']").prop("disabled", true);
+ }
+}
+
+class InvalidCSSIDSelector extends Error {
+ constructor(message) {
+ super(message);
+ this.name = "InvalidCSSIDSelector";
+ }
+}
+
+class InvalidDataAttributeName extends Error {
+ constructor(message) {
+ super(message);
+ this.name = "InvalidDataAttributeName";
+ }
+}
+
+/**
+ * CSSIDSelector: A CSS ID Selector
+ * @param {String} A CSS selector of the form '#...'
+ */
+class CSSIDSelector {
+ constructor(selector) {
+ if(!selector.startsWith("#")) {
+ throw new InvalidCSSIDSelector(
+ "Expected the CSS selector to begin with a `#` character.");
+ }
+ let id_str = selector.slice(1, selector.length);
+ if(document.getElementById(id_str) == null) {
+ throw new InvalidCSSIDSelector(
+ "Element with ID '" + id_str + "' does not exist.");
+ }
+ this.selector = selector;
+ }
+}
+
+/**
+ * TableDataSource: A type to represent a table's data source
+ * @param {String} A CSS selector for an ID
+ * @param {String} A `data-*` attribute name
+ */
+class TableDataSource {
+ constructor(table_id, data_attribute_name) {
+ this.table_id = new CSSIDSelector(table_id);
+ let data = document.querySelector(
+ table_id).getAttribute(data_attribute_name);
+ if(data == null) {
+ throw new InvalidDataAttributeName(
+ "data-* attribute '" + data_attribute_name + "' does not exist " +
+ "for table with ID '" + table_id.slice(1, table_id.length) +
+ "'.");
+ } else {
+ this.data_attribute_name = data_attribute_name;
+ }
+ }
+}
+
+/**
+ * Switch the dataset from search table to selection table and vice versa
+ * @param {Object} A genotype dataset
+ * @param {TableDataSource} The table to switch the dataset from
+ * @param {TableDataSource} The table to switch the dataset to
+ */
+function select_deselect_dataset(dataset, source, destination) {
+ dest_selector = destination.table_id.selector
+ dest_data = JSON.parse(
+ $(dest_selector).attr(destination.data_attribute_name));
+ add_to_table_data(dataset, destination); // Add to destination table
+ remove_from_table_data(dataset, source); // Remove from source table
+ /***** BEGIN: Re-render tables *****/
+ // The `render_table` could be modified to use the checkbox creator function
+ // from the `TableDataSource` object, once that is modified to have that.
+ render_table(
+ "#tbl-link-genotypes", "data-selected-datasets", link_checkbox);
+ render_table("#tbl-genotypes", "data-datasets", search_checkbox);
+ toggle_link_button();
+ /***** END: Re-render tables *****/
+}
+
+$(document).ready(function() {
+ let search_table = new TableDataSource("#tbl-genotypes", "data-datasets");
+ let link_table = new TableDataSource(
+ "#tbl-link-genotypes", "data-selected-datasets");
+
+ $("#frm-search-traits").submit(function(event) {
+ event.preventDefault();
+ return false;
+ });
+
+ /* $("#txt-query").keyup(debounced_search()); */
+
+ $("#tbl-genotypes").on("change", ".checkbox-search", function(event) {
+ if(this.checked) {
+ select_deselect_dataset(
+ JSON.parse(this.value), search_table, link_table);
+ }
+ });
+
+ $("#tbl-link-genotypes").on("change", ".checkbox-selected", function(event) {
+ if(!this.checked) {
+ select_deselect_dataset(
+ JSON.parse(this.value), link_table, search_table);
+ }
+ });
+});
diff --git a/wqflask/wqflask/templates/oauth2/data-list-genotype.html b/wqflask/wqflask/templates/oauth2/data-list-genotype.html
index 3ef810ec..3b7cfc1f 100644
--- a/wqflask/wqflask/templates/oauth2/data-list-genotype.html
+++ b/wqflask/wqflask/templates/oauth2/data-list-genotype.html
@@ -54,7 +54,7 @@
{%for dataset in selected_datasets%}
-
@@ -151,102 +151,6 @@
{%endblock%}
{%block js%}
-
+
{%endblock%}
--
cgit v1.2.3
From 1383ab160b302601211b28db3e6a9ad97e53f8e3 Mon Sep 17 00:00:00 2001
From: Frederick Muriuki Muriithi
Date: Sat, 8 Apr 2023 08:07:58 +0300
Subject: oauth2: Link the Genotype datasets.
---
wqflask/wqflask/oauth2/client.py | 9 ++++++--
wqflask/wqflask/oauth2/data.py | 26 ++++++++++++++++++++++
.../templates/oauth2/data-list-genotype.html | 5 ++++-
3 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/wqflask/wqflask/oauth2/client.py b/wqflask/wqflask/oauth2/client.py
index 999bbfc8..0cecd965 100644
--- a/wqflask/wqflask/oauth2/client.py
+++ b/wqflask/wqflask/oauth2/client.py
@@ -1,4 +1,5 @@
"""Common oauth2 client utilities."""
+from typing import Optional
from urllib.parse import urljoin
from flask import session, current_app as app
@@ -29,13 +30,17 @@ def oauth2_get(uri_path: str, data: dict = {}) -> Either:
return Left(resp)
-def oauth2_post(uri_path: str, data: dict) -> Either:
+def oauth2_post(
+ uri_path: str, data: Optional[dict] = None, json: Optional[dict] = None,
+ **kwargs) -> Either:
token = session.get("oauth2_token")
config = app.config
client = OAuth2Session(
config["OAUTH2_CLIENT_ID"], config["OAUTH2_CLIENT_SECRET"],
token=token, scope=SCOPE)
- resp = client.post(urljoin(config["GN_SERVER_URL"], uri_path), data=data)
+ resp = client.post(
+ urljoin(config["GN_SERVER_URL"], uri_path), data=data, json=json,
+ **kwargs)
if resp.status_code == 200:
return Right(resp.json())
diff --git a/wqflask/wqflask/oauth2/data.py b/wqflask/wqflask/oauth2/data.py
index 1dcacb7e..e1b33b56 100644
--- a/wqflask/wqflask/oauth2/data.py
+++ b/wqflask/wqflask/oauth2/data.py
@@ -1,4 +1,5 @@
"""Handle linking data to groups."""
+import json
from urllib.parse import urljoin
from flask import (
@@ -154,3 +155,28 @@ def link_data():
except AssertionError as aserr:
flash("You must provide all the expected data.", "alert-danger")
return redirect(url_for("oauth2.data.list_data"))
+
+@data.route("/link/genotype", methods=["POST"])
+def link_genotype_data():
+ """Link genotype data to a group."""
+ form = request.form
+ link_source_url = redirect(url_for("oauth2.data.list_data"))
+ if bool(form.get("species_name")):
+ link_source_url = redirect(url_for(
+ "oauth2.data.list_data_by_species_and_dataset",
+ species_name=form["species_name"], dataset_type="genotype"))
+
+ def __link_error__(err):
+ flash(f"{err['error']}: {err['error_description']}", "alert-danger")
+ return link_source_url
+
+ def __link_success__(success):
+ flash(success["description"], "alert-success")
+ return link_source_url
+
+ return oauth2_post("oauth2/data/link/genotype", json={
+ "species_name": form.get("species_name"),
+ "group_id": form.get("group_id"),
+ "selected_datasets": tuple(json.loads(dataset) for dataset
+ in form.getlist("selected_datasets"))
+ }).either(lambda err: __link_error__(process_error(err)), __link_success__)
diff --git a/wqflask/wqflask/templates/oauth2/data-list-genotype.html b/wqflask/wqflask/templates/oauth2/data-list-genotype.html
index 3b7cfc1f..54479251 100644
--- a/wqflask/wqflask/templates/oauth2/data-list-genotype.html
+++ b/wqflask/wqflask/templates/oauth2/data-list-genotype.html
@@ -23,9 +23,12 @@