From 204a308be0f741726b9a620d88fbc22b22124c81 Mon Sep 17 00:00:00 2001 From: Arun Isaac Date: Fri, 29 Dec 2023 18:55:37 +0000 Subject: Namespace all modules under gn2. We move all modules under a gn2 directory. This is important for "correct" packaging and deployment as a Guix service. --- gn2/wqflask/static/new/javascript/auth/search.js | 172 +++++++++++++++++++ .../static/new/javascript/auth/search_genotypes.js | 95 +++++++++++ .../static/new/javascript/auth/search_mrna.js | 97 +++++++++++ .../new/javascript/auth/search_phenotypes.js | 188 +++++++++++++++++++++ 4 files changed, 552 insertions(+) create mode 100644 gn2/wqflask/static/new/javascript/auth/search.js create mode 100644 gn2/wqflask/static/new/javascript/auth/search_genotypes.js create mode 100644 gn2/wqflask/static/new/javascript/auth/search_mrna.js create mode 100644 gn2/wqflask/static/new/javascript/auth/search_phenotypes.js (limited to 'gn2/wqflask/static/new/javascript/auth') diff --git a/gn2/wqflask/static/new/javascript/auth/search.js b/gn2/wqflask/static/new/javascript/auth/search.js new file mode 100644 index 00000000..d094cebf --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auth/search.js @@ -0,0 +1,172 @@ +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, checkbox_creation_function) { + 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; + } + this.checkbox_creation_function = checkbox_creation_function; + } +} + +/** + * Render the table + * @param {String} The selector for the table's ID + * @param {String} The name of the data-* attribute holding the table's data + * @param {Function} The function to call to generate the appropriate checkbox + */ +function render_table(table_data_source) { + table_id = table_data_source.table_id.selector; + data_attr_name = table_data_source.data_attribute_name; + $(table_id + " tbody tr").remove(); + table_data = JSON.parse($(table_id).attr(data_attr_name)).sort((d1, d2) => { + return (d1.dataset_name > d2.dataset_name ? 1 : ( + d1.dataset_name < d2.dataset_name ? -1 : 0)) + }); + if(table_data.length < 1) { + row = $("") + 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(table_data_source.checkbox_creation_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(items, filter_fn) { + return items.filter(filter_fn).length > 0; +} + +function remove_from_table_data(dataset, table_data_source, filter_fn) { + 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( + filter_fn); + $(table_id).attr(data_attr_name, JSON.stringify(without_dataset)); +} + +function add_to_table_data(dataset, table_data_source, filter_fn) { + 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(table_data, filter_fn)) { + table_data.push(dataset); + } + $(table_id).attr(data_attr_name, JSON.stringify(Array.from(table_data))); +} + +/** + * Switch the dataset/trait from search table to selection table and vice versa + * @param {Object} A dataset/trait object + * @param {TableDataSource} The source table for the dataset/trait + * @param {TableDataSource} The destination table for the dataset/trait + */ +function select_deselect(item, source, destination, filter_fn, render_fn=render_table) { + dest_selector = destination.table_id.selector + dest_data = JSON.parse( + $(dest_selector).attr(destination.data_attribute_name)); + add_to_table_data(item, destination, filter_fn); // Add to destination table + remove_from_table_data(item, source, (arg) => {return !filter_fn(arg)}); // Remove from source table + /***** BEGIN: Re-render tables *****/ + render_fn(destination); + render_fn(source); + /***** END: Re-render tables *****/ +} + +function debounce(func, delay=500) { + var timeout; + return function search(event) { + clearTimeout(timeout); + timeout = setTimeout(func, delay); + }; +} + +/** + * Build a checkbox + * @param {Dataset Object} A JSON.stringify-able object + * @param {String} The name to assign the checkbox + */ +function build_checkbox(data_object, checkbox_name, checkbox_aux_classes="", checked=false) { + cell = $(""); + check = $( + ''); + check.val(JSON.stringify(data_object)); + check.prop("checked", checked); + auxilliary_classes = checkbox_aux_classes.trim(); + if(Boolean(auxilliary_classes)) { + check.attr("class", + check.attr("class") + " " + auxilliary_classes.trim()); + } + cell.append(check); + return cell; +} + +function link_checkbox(dataset) { + return build_checkbox(dataset, "selected", "checkbox-selected", true); +} + +function search_checkbox(dataset) { + return build_checkbox(dataset, "search_datasets", "checkbox-search"); +} + +function table_cell(value) { + cell = $(""); + cell.html(value); + return cell; +} diff --git a/gn2/wqflask/static/new/javascript/auth/search_genotypes.js b/gn2/wqflask/static/new/javascript/auth/search_genotypes.js new file mode 100644 index 00000000..d1b8ed9e --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auth/search_genotypes.js @@ -0,0 +1,95 @@ +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); + } +} + +function search_genotypes() { + query = document.getElementById("txt-query").value; + selected = JSON.parse(document.getElementById( + "tbl-link-genotypes").getAttribute("data-selected-datasets")); + species_name = document.getElementById("txt-species-name").value + search_endpoint = "/auth/data/genotype/search" + search_table = new TableDataSource( + "#tbl-genotypes", "data-datasets", search_checkbox); + $.ajax( + search_endpoint, + { + "method": "POST", + "contentType": "application/json; charset=utf-8", + "dataType": "json", + "data": JSON.stringify({ + "query": query, + "selected": selected, + "dataset_type": "genotype", + "species_name": species_name}), + "error": function(jqXHR, textStatus, errorThrown) { + data = jqXHR.responseJSON + elt = document.getElementById("search-error").setAttribute( + "style", "display: block;"); + document.getElementById("search-error-text").innerHTML = ( + data.error + " (" + data.status_code + "): " + + data.error_description); + document.getElementById("tbl-genotypes").setAttribute( + "data-datasets", JSON.stringify([])); + render_table(search_table); + }, + "success": function(data, textStatus, jqXHR) { + document.getElementById("search-error").setAttribute( + "style", "display: none;"); + document.getElementById("tbl-genotypes").setAttribute( + "data-datasets", JSON.stringify(data)); + render_table(search_table); + } + }); +} + +/** + * Return function to check whether `dataset` is in array of `datasets`. + * @param {GenotypeDataset} A genotype dataset. + * @param {Array} An array of genotype datasets. + */ +function make_filter(trait) { + return (dst) => { + return (dst.SpeciesId == dataset.SpeciesId && + dst.InbredSetId == dataset.InbredSetId && + dst.GenoFreezeId == dataset.GenoFreezeId); + }; +} + +$(document).ready(function() { + let search_table = new TableDataSource( + "#tbl-genotypes", "data-datasets", search_checkbox); + let link_table = new TableDataSource( + "#tbl-link-genotypes", "data-selected-datasets", link_checkbox); + + $("#frm-search-traits").submit(function(event) { + event.preventDefault(); + return false; + }); + + $("#txt-query").keyup(debounce(search_genotypes)); + + $("#tbl-genotypes").on("change", ".checkbox-search", function(event) { + if(this.checked) { + dataset = JSON.parse(this.value); + select_deselect( + dataset, search_table, link_table, make_filter(dataset)); + toggle_link_button(); + } + }); + + $("#tbl-link-genotypes").on("change", ".checkbox-selected", function(event) { + if(!this.checked) { + dataset = JSON.parse(this.value); + select_deselect( + dataset, link_table, search_table, make_filter(dataset)); + toggle_link_button(); + } + }); +}); diff --git a/gn2/wqflask/static/new/javascript/auth/search_mrna.js b/gn2/wqflask/static/new/javascript/auth/search_mrna.js new file mode 100644 index 00000000..3eca4ed2 --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auth/search_mrna.js @@ -0,0 +1,97 @@ +function toggle_link_button() { + num_groups = $("#frm-link select option").length - 1; + num_selected = JSON.parse( + $("#tbl-link").attr("data-datasets")).length; + if(num_groups > 0 && num_selected > 0) { + $("#frm-link input[type='submit']").prop("disabled", false); + } else { + $("#frm-link input[type='submit']").prop("disabled", true); + } +} + +function search_mrna() { + query = document.getElementById("txt-query").value; + selected = JSON.parse(document.getElementById( + "tbl-link").getAttribute("data-datasets")); + species_name = document.getElementById("txt-species-name").value + search_endpoint = "/auth/data/mrna/search" + search_table = new TableDataSource( + "#tbl-search", "data-datasets", search_checkbox); + $.ajax( + search_endpoint, + { + "method": "POST", + "contentType": "application/json; charset=utf-8", + "dataType": "json", + "data": JSON.stringify({ + "query": query, + "selected": selected, + "dataset_type": "mrna", + "species_name": species_name}), + "error": function(jqXHR, textStatus, errorThrown) { + error_data = jqXHR.responseJSON + console.debug("ERROR_DATA:", error_data); + elt = document.getElementById("search-error").setAttribute( + "style", "display: block;"); + document.getElementById("search-error-text").innerHTML = ( + error_data.error + " (" + error_data.status_code + "): " + + error_data.error_description); + document.getElementById("tbl-search").setAttribute( + "data-datasets", JSON.stringify([])); + render_table(search_table); + }, + "success": function(data, textStatus, jqXHR) { + document.getElementById("search-error").setAttribute( + "style", "display: none;"); + document.getElementById("tbl-search").setAttribute( + "data-datasets", JSON.stringify(data)); + render_table(search_table); + } + }); +} + +/** + * Make function to check whether `dataset` is in array of `datasets`. + * @param {mRNADataset} A mrna dataset. + * @param {Array} An array of mrna datasets. + */ +function make_filter(dataset) { + return (dst) => { + return (dst.SpeciesId == dataset.SpeciesId && + dst.InbredSetId == dataset.InbredSetId && + dst.ProbeFreezeId == dataset.ProbeFreezeId && + dst.ProbeSetFreezeId == dataset.ProbeSetFreezeId); + }; +} + +$(document).ready(function() { + let search_table = new TableDataSource( + "#tbl-search", "data-datasets", search_checkbox); + let link_table = new TableDataSource( + "#tbl-link", "data-datasets", link_checkbox); + + $("#frm-search").submit(function(event) { + event.preventDefault(); + return false; + }); + + $("#txt-query").keyup(debounce(search_mrna)); + + $("#tbl-search").on("change", ".checkbox-search", function(event) { + if(this.checked) { + dataset = JSON.parse(this.value); + select_deselect( + dataset, search_table, link_table, make_filter(dataset)); + toggle_link_button(); + } + }); + + $("#tbl-link").on("change", ".checkbox-selected", function(event) { + if(!this.checked) { + dataset = JSON.parse(this.value); + select_deselect( + dataset, link_table, search_table, make_filter(dataset)); + toggle_link_button(); + } + }); +}); diff --git a/gn2/wqflask/static/new/javascript/auth/search_phenotypes.js b/gn2/wqflask/static/new/javascript/auth/search_phenotypes.js new file mode 100644 index 00000000..99ecb16e --- /dev/null +++ b/gn2/wqflask/static/new/javascript/auth/search_phenotypes.js @@ -0,0 +1,188 @@ +/** + * Global variables: Bad idea - figure out how to pass them down a call stack. + */ +search_table = new TableDataSource( + "#tbl-phenotypes", "data-traits", (trait) => { + return build_checkbox(trait, "search_traits", "checkbox-search"); + }); +link_table = new TableDataSource( + "#tbl-link-phenotypes", "data-traits", (trait) => { + return build_checkbox( + trait, "selected", "checkbox-selected", checked=true); + }); + +/** + * Toggle the state for the "Link Traits" button + */ +function toggle_link_button() { + num_groups = $("#frm-link-phenotypes select option").length - 1; + num_selected = JSON.parse( + $("#tbl-link-phenotypes").attr("data-traits")).length; + if(num_groups > 0 && num_selected > 0) { + $("#frm-link-phenotypes input[type='submit']").prop("disabled", false); + } else { + $("#frm-link-phenotypes input[type='submit']").prop("disabled", true); + } +} + +/** + * Default error function: print out debug messages + */ +function default_error_fn(jqXHR, textStatus, errorThrown) { + console.debug("XHR:", jqXHR); + console.debug("STATUS:", textStatus); + console.debug("ERROR:", errorThrown); +} + +/** + * Render the table(s) for the phenotype traits + * @param {TableDataSource} The table to render + */ +function render_pheno_table(table_data_source) { + table_id = table_data_source.table_id.selector; + data_attr_name = table_data_source.data_attribute_name; + $(table_id + " tbody tr").remove(); + table_data = JSON.parse($(table_id).attr(data_attr_name)).sort((t1, t2) => { + return (t1.name > t2.name ? 1 : (t1.name < t2.name ? -1 : 0)) + }); + if(table_data.length < 1) { + row = $("") + cell = $(''); + cell.append( + $('')); + cell.append(" "); + cell.append("No phenotype traits to select from."); + row.append(cell); + $(table_id + " tbody").append(row); + } + table_data.forEach(function(trait) { + row = $("") + row.append(table_data_source.checkbox_creation_function(trait)); + row.append(table_cell(trait.name)); + row.append(table_cell(trait.group)); + row.append(table_cell(trait.dataset)); + row.append(table_cell(trait.dataset_fullname)); + row.append(table_cell(trait.description)); + row.append(table_cell(trait.authors.join(", "))); + row.append(table_cell( + '' + + trait.year + "")); + row.append(table_cell("Chr:" + trait.geno_chr + "@" + trait.geno_mb)); + row.append(table_cell(trait.lrs)); + row.append(table_cell(trait.additive)); + $(table_id + " tbody").append(row); + }); +} + +function display_search_results(data, textStatus, jqXHR) { + if(data.status == "queued" || data.status == "started") { + setTimeout(() => { + fetch_search_results(data.job_id, display_search_results); + }, 250); + return; + } + if(data.status == "completed") { + $("#tbl-phenotypes").attr( + "data-traits", JSON.stringify(data.search_results)); + // Remove this reference to global variable + render_pheno_table(search_table); + } + $("#txt-search").prop("disabled", false); +} + +/** + * Fetch the search results + * @param {UUID}: The job id to fetch data for + */ +function fetch_search_results(job_id, success, error=default_error_fn) { + host = $("#frm-search-traits").attr("data-gn-server-url"); + endpoint = host + "auth/data/search/phenotype/" + job_id + $("#txt-search").prop("disabled", true); + $.ajax( + endpoint, + { + "method": "GET", + "contentType": "application/json; charset=utf-8", + "dataType": "json", + "error": error, + "success": success + } + ); +} + +function search_phenotypes() { + query = document.getElementById("txt-query").value; + selected = JSON.parse(document.getElementById( + "tbl-link-phenotypes").getAttribute("data-traits")); + species_name = document.getElementById("txt-species-name").value + per_page = document.getElementById("txt-per-page").value + search_table = new TableDataSource( + "#tbl-phenotypes", "data-traits", search_checkbox); + endpoint = "/auth/data/phenotype/search" + $.ajax( + endpoint, + { + "method": "POST", + "contentType": "application/json; charset=utf-8", + "dataType": "json", + "data": JSON.stringify({ + "query": query, + "species_name": species_name, + "dataset_type": "phenotype", + "per_page": per_page, + "selected_traits": selected + }), + "error": default_error_fn, + "success": (data, textStatus, jqXHR) => { + fetch_search_results(data.job_id, display_search_results); + } + }); +} + +/** + * Return a function to check whether `trait` is in array of `traits`. + * @param {PhenotypeTrait} A phenotype trait. + * @param {Array} An array of phenotype traits. + */ +function make_filter(trait) { + return (trt) => { + return (trt.species == trait.species && + trt.group == trait.group && + trt.dataset == trait.dataset && + trt.name == trait.name); + }; +} + +$(document).ready(function() { + $("#frm-search-traits").submit(event => { + event.preventDefault(); + return false; + }); + + $("#txt-query").keyup(debounce(search_phenotypes)); + + $("#tbl-link-phenotypes").on("change", ".checkbox-selected", function(event) { + if(!this.checked) { + trait = JSON.parse(this.value); + select_deselect(trait, link_table, search_table, + make_filter(trait), render_pheno_table); + toggle_link_button(); + } + }); + + $("#tbl-phenotypes").on("change", ".checkbox-search", function(event) { + if(this.checked) { + trait = JSON.parse(this.value) + select_deselect(trait, search_table, link_table, + make_filter(trait), render_pheno_table); + toggle_link_button(); + } + }); + + setTimeout(() => { + fetch_search_results( + $("#tbl-phenotypes").attr("data-initial-job-id"), + display_search_results); + }, 500); +}); -- cgit v1.2.3