diff --git a/Gemfile b/Gemfile index a95781d5c..a99a2d45a 100644 --- a/Gemfile +++ b/Gemfile @@ -13,10 +13,9 @@ gem 'terser' #ugilifer replacent # gem 'duktape' gem 'bootstrap', '~> 4.2.0' -gem 'chart-js-rails' gem 'jquery-rails' gem 'jquery-ui-rails' -gem 'select2-rails' + # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] @@ -118,6 +117,7 @@ group :development do gem 'i18n-tasks' gem 'deepl-rb' gem 'letter_opener_web', '~> 2.0' + gem 'haml-rails' end group :test, :development do diff --git a/Gemfile.lock b/Gemfile.lock index ca831144e..8a4b95c41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -137,8 +137,6 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - chart-js-rails (0.1.7) - railties (> 3.1) coderay (1.1.3) concurrent-ruby (1.2.2) crass (1.0.6) @@ -148,7 +146,7 @@ GEM daemons (1.4.1) dalli (3.2.6) date (3.3.4) - debug (1.9.0) + debug (1.9.1) irb (~> 1.10) reline (>= 0.3.8) deepl-rb (2.5.3) @@ -159,7 +157,7 @@ GEM erubi (1.12.0) erubis (2.7.0) eventmachine (1.2.7) - excon (0.108.0) + excon (0.109.0) execjs (2.9.1) faraday (2.0.1) faraday-net_http (~> 2.0) @@ -178,7 +176,7 @@ GEM flamegraph (0.9.5) globalid (1.2.1) activesupport (>= 6.1) - graphql (2.2.0) + graphql (2.2.4) racc (~> 1.4) graphql-client (0.18.0) activesupport (>= 3.0) @@ -186,6 +184,11 @@ GEM haml (5.2.2) temple (>= 0.8.0) tilt + haml-rails (2.1.0) + actionpack (>= 5.1) + activesupport (>= 5.1) + haml (>= 4.0.6) + railties (>= 5.1) hashie (5.0.0) highline (2.1.0) html2haml (2.3.0) @@ -212,7 +215,7 @@ GEM rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) iconv (1.0.8) - importmap-rails (1.2.3) + importmap-rails (2.0.1) actionpack (>= 6.0.0) activesupport (>= 6.0.0) railties (>= 6.0.0) @@ -220,7 +223,7 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.7.1) - irb (1.10.1) + irb (1.11.0) rdoc reline (>= 0.3.8) iso-639 (0.3.6) @@ -233,9 +236,10 @@ GEM jsbundling-rails (1.2.1) railties (>= 6.0.0) json (2.7.1) - json-jwt (1.16.3) + json-jwt (1.16.5) activesupport (>= 4.2) aes_key_wrap + base64 bindata faraday (~> 2.0) faraday-follow_redirects @@ -278,7 +282,7 @@ GEM marcel (1.0.2) matrix (0.4.2) method_source (1.0.0) - mime-types (3.5.1) + mime-types (3.5.2) mime-types-data (~> 3.2015) mime-types-data (3.2023.1205) mini_mime (1.1.5) @@ -287,13 +291,14 @@ GEM multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.3.0) + mutex_m (0.2.0) mysql2 (0.5.5) - net-ftp (0.2.0) + net-ftp (0.2.1) net-protocol time net-http (0.3.2) uri - net-imap (0.4.8) + net-imap (0.4.9) date net-protocol net-pop (0.1.2) @@ -304,7 +309,7 @@ GEM net-ssh (>= 2.6.5, < 8.0.0) net-smtp (0.4.0) net-protocol - net-ssh (7.2.0) + net-ssh (7.2.1) netrc (0.11.0) newrelic_rpm (9.6.0) base64 @@ -322,7 +327,7 @@ GEM version_gem (~> 1.1) oj (3.16.3) bigdecimal (>= 3.0) - omniauth (2.1.1) + omniauth (2.1.2) hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection @@ -366,7 +371,8 @@ GEM rack (>= 0.4) rack-mini-profiler (3.3.0) rack (>= 1.2.0) - rack-protection (3.1.0) + rack-protection (3.2.0) + base64 (>= 0.1.0) rack (~> 2.2, >= 2.2.4) rack-test (2.1.0) rack (>= 1.3) @@ -470,7 +476,6 @@ GEM sprockets (> 3.0) sprockets-rails tilt - select2-rails (4.0.13) selenium-webdriver (4.9.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) @@ -497,11 +502,12 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sshkit (1.21.6) + sshkit (1.21.7) + mutex_m net-scp (>= 1.1.2) net-ssh (>= 2.8.0) stackprof (0.2.25) - stimulus-rails (1.3.0) + stimulus-rails (1.3.3) railties (>= 6.0.0) temple (0.10.3) terminal-table (3.0.2) @@ -568,7 +574,6 @@ DEPENDENCIES capistrano-rails (~> 1.4) capistrano-yarn capybara - chart-js-rails cube-ruby dalli debug @@ -578,6 +583,7 @@ DEPENDENCIES flamegraph graphql-client haml (~> 5.1) + haml-rails html2haml i18n i18n-tasks @@ -618,7 +624,6 @@ DEPENDENCIES rspec-rails rubocop sassc-rails - select2-rails selenium-webdriver simplecov simplecov-cobertura diff --git a/app/assets/images/icons/arrow-down.svg b/app/assets/images/icons/arrow-down.svg new file mode 100644 index 000000000..1130bff60 --- /dev/null +++ b/app/assets/images/icons/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/icons/details.svg b/app/assets/images/icons/details.svg new file mode 100644 index 000000000..ea7ce0527 --- /dev/null +++ b/app/assets/images/icons/details.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/icons/hide.svg b/app/assets/images/icons/hide.svg new file mode 100644 index 000000000..8cbc7b1ba --- /dev/null +++ b/app/assets/images/icons/hide.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/images/icons/popup-link.svg b/app/assets/images/icons/popup-link.svg index d094245c5..f8070b3e9 100644 --- a/app/assets/images/icons/popup-link.svg +++ b/app/assets/images/icons/popup-link.svg @@ -1,3 +1,4 @@ - + diff --git a/app/assets/images/icons/reuses.svg b/app/assets/images/icons/reuses.svg new file mode 100644 index 000000000..45de94ca9 --- /dev/null +++ b/app/assets/images/icons/reuses.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/icons/ror.svg b/app/assets/images/icons/ror.svg new file mode 100644 index 000000000..b6b18c2c1 --- /dev/null +++ b/app/assets/images/icons/ror.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/assets/images/icons/search.svg b/app/assets/images/icons/search.svg new file mode 100644 index 000000000..e946dbae7 --- /dev/null +++ b/app/assets/images/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/icons/settings.svg b/app/assets/images/icons/settings.svg new file mode 100644 index 000000000..d8956143e --- /dev/null +++ b/app/assets/images/icons/settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/icons/visualize.svg b/app/assets/images/icons/visualize.svg new file mode 100644 index 000000000..f13b44206 --- /dev/null +++ b/app/assets/images/icons/visualize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 93e37e762..df70491a3 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,21 +13,14 @@ //= require bioportal //= require admin/licenses //= require bp_ajax_controller -//= require bp_reviews //= require bp_notes -//= require bp_ontolobridge //= require bp_form_complete -//= require bp_analytics -//= require bp_search //= require bp_mappings //= require bp_admin //= require bp_recommender -//= require bp_property_tree //= require concepts -//= require home //= require_tree ./helpers //= require_tree ./components -//= require ontologies //= require projects //= require application_esbuild customElements.define('data-table-loader', DataTableLoader ); diff --git a/app/assets/javascripts/bioportal.js.erb b/app/assets/javascripts/bioportal.js.erb index 5d679af8a..13332a531 100644 --- a/app/assets/javascripts/bioportal.js.erb +++ b/app/assets/javascripts/bioportal.js.erb @@ -1,652 +1,153 @@ // BioPortal jQuery Namespace -jQuery.bioportal = {}; +jQuery.bioportal = {} -// Backport function name -jQuery.curCSS = jQuery.css; // CSRF protection support -$(document).ajaxSend(function(e, xhr, options) { - var token = $("meta[name='csrf-token']").attr('content'); - xhr.setRequestHeader('X-CSRF-Token', token); -}); - -// Cache Implementation -var cache = new Object(); -var que= new Array(); -var queIndex = 0; -var thread=0; - -// Invalidate and Refetch - function refreshCache(nodeID){ - cache[nodeID]=null - queData([nodeID],currentOntology) - } - - -// Cache Getter - function getCache(nodeID){ - if(cache[nodeID]!=null){ - return cache[nodeID] - }else{ - return null; - } - } -// Cache Setter - function setCache(nodeID,content){ - cache[nodeID]=content - } - -// Starts the prefetching - function queData(nodes,ontology){ - currentOntology = ontology - // Disables Cache - return false; - - que = nodes.concat(que) - // set how many threads you want fetching data - queIndex = 0 - thread++ - preFetchData(que[queIndex],ontology,thread); - //preFetchData(que[1],ontology) - - } - -// The prefetching function - function preFetchData(node_id,ontology,threadNumber) { - - - var responseSuccess = function(o) - { - var respTxt = o.responseText; - tabData = respTxt.split("|||") - setCache(node_id,tabData) - queIndex++ - - // makes sure the que isnt complete and makes sure that this thread shouldnt die - - if(queIndex < que.length && thread == threadNumber){ - preFetchData(que[queIndex],ontology,threadNumber) - }else if(queIndex >= que.length){ - que = new Array(); - } - - } - - var responseFailure = function(o){ - - } - - var callback = - { - success:responseSuccess, - failure:responseFailure - }; - - - // see's if item is already in cache, if not it makes the ajax call - if(getCache(node_id)==null){ - YAHOO.util.Connect.asyncRequest('GET','/'+ontology+'/'+node_id+"?callback=load",callback); - }else{ - queIndex++ - if(queIndex < que.length && thread == threadNumber){ - preFetchData(que[queIndex],ontology,threadNumber) - }else if(queIndex >= que.length){ - que = new Array(); - } - } - - } - - - - -//------------------------------- - - -function toggleHide(id,name_to_hide){ - toggle = true; - element = document.getElementById(id); - if(element.style.display==""){ - toggle = false; - } - - if(name_to_hide !=null && name_to_hide != ""){ - elements = document.getElementsByName(name_to_hide); - for( var x = 0; x'); - YAHOO.wait.container.wait.render(document.body); - - - -} - -function buildTabWait(){ - YAHOO.namespace("tabwait.container"); - // Initialize the temporary Panel to display while waiting for external content to load - YAHOO.tabwait.container.wait = new YAHOO.widget.Panel("tabwait", - { width:"240px", - fixedcenter:true, - close:false, - draggable:false, - zindex:4, - modal:true, - visible:false - } - ); - - YAHOO.tabwait.container.wait.setHeader("Building Tree, please wait..."); - YAHOO.tabwait.container.wait.setBody(''); - YAHOO.tabwait.container.wait.render(document.body); - - - -} - - - -function buildSearchWait(){ - YAHOO.namespace("wait.container"); - // Initialize the temporary Panel to display while waiting for external content to load - YAHOO.wait.container.wait = new YAHOO.widget.Panel("wait", - { width:"240px", - fixedcenter:true, - close:false, - draggable:false, - zindex:4, - modal:true, - visible:false - } - ); - - YAHOO.wait.container.wait.setHeader("Searching, please wait..."); - YAHOO.wait.container.wait.setBody(''); - YAHOO.wait.container.wait.render(document.body); - - - -} - - - -// Selects the current clicked node. -function toggleSelected(node){ - - var index=1; - - // can get in endless loop if you jump to a node that is free floating. - nullCount = 0; - while (nullCount < 20){ - if(tree.getNodeByIndex(index)!=null){ - if (tree.getNodeByIndex(index).labelStyle=='ygtvlabel-selected'){ - tree.getNodeByIndex(index).labelStyle='ygtvlabel' - break; - } - }else{ - nullCount ++; - } - index++; - } - node.labelStyle="ygtvlabel-selected"; - -} -var tabs=null; - function buildTabs(){ - tabs = new YAHOO.widget.TabView('tabframe'); - - //YAHOO.namespace("feed"); - //YAHOO.feed.feed = new YAHOO.widget.Panel("feedPanel", { context:["feed","tr","br"], width:"320px", visible:false,draggable:false,constraintoviewport:true } ); - //YAHOO.feed.feed.render(); - //YAHOO.util.Event.addListener("feed", "click", YAHOO.feed.feed.show, YAHOO.feed.feed, true); - - var split = new Ext.SplitBar("dragSpot", "leftbar", - Ext.SplitBar.HORIZONTAL, Ext.SplitBar.LEFT); - split.setAdapter(new Ext.SplitBar.AbsoluteLayoutAdapter("container")); - split.minSize = 100; - split.maxSize = 400; - split.animate = true; - split.on('moved', splitterMoved); - } - - function splitterMoved(splitbar,newSize){ - var rightSide = Ext.get('centerContent'); - var tabFrame = Ext.get('tabframe'); - var leftSide = Ext.get('leftbar'); - - } - - function resetNoteForm(uniq){ - document.getElementById("note_subject"+uniq).value="" - document.getElementById("note_comment"+uniq).value="" - document.getElementById('noteParent'+uniq).value=""; - } - - -var myEditor; - - - -function buildEditor(uniq){ - - - - var Dom = YAHOO.util.Dom, - Event = YAHOO.util.Event; - - myEditor = new YAHOO.widget.SimpleEditor('note_comment'+uniq, { - height: '300px', - width: '522px', - dompath: false //Turns on the bar at the bottom -}); -myEditor.render(); -} - -function saveNote(){ - myEditor.saveHTML(); - myEditor.destroy(); - -} - -function destroyEditor(){ - myEditor.clearEditorDoc(); - myEditor.destroy(); - -} - - - - -function updateOntologyList(ontology){ - list = document.getElementById("ontologieslist") - if(ontology.checked){ - var s= document.createElement("LI"); - var v= document.createElement("INPUT"); - v.type="hidden" - v.value=1 - v.name=ontology.name - v.id="hid_"+ontology.nextSibling.nodeValue; - s.id="id_"+ontology.nextSibling.nodeValue; - s.innerHTML= ontology.nextSibling.nodeValue - list.appendChild(s); - list.appendChild(v); - ontology.parentNode.innerHTML = ontology.parentNode.innerHTML.replace(">","checked >") - }else{ - list.removeChild(document.getElementById("id_"+ontology.nextSibling.nodeValue)) - list.removeChild(document.getElementById("hid_"+ontology.nextSibling.nodeValue)) - ontology.parentNode.innerHTML = ontology.parentNode.innerHTML.replace("checked","") - } - - -} - -function hover_on_BG(cell){ - if(!cell.firstChild.checked){ - cell.style.background="#DFDFDF"; - } -} - -function hover_off_BG(cell){ - if(!cell.firstChild.checked){ - cell.style.background="white"; - } -} - -var dialog; - -function toggleBG(cell,bgcolor){ - - if(cell.firstChild.checked){ - cell.style.backgroundColor=bgcolor; - }else{ - cell.style.backgroundColor=bgcolor; - } - - -} - -function updateContent(){ - document.getElementById('ontologies').innerHTML = Dialog.dialog.getContent().innerHTML -} - -//------------------------------------- JQuery Rewrite Functions --------------------------------- - -function ajaxForm(form, target, callback) { - // let's start the jQuery while I wait. - // step 1: onload - capture the submit event on the form. - - // now we're going to capture *all* the fields in the - // form and submit it via ajax. - - // :input is a macro that grabs all input types, select boxes - // textarea, etc. Then I'm using the context of the form from - // the initial '#contactForm' to narrow down our selector - var inputs = []; - - jQuery(form).find(':input').each(function() { - if (this.type == "checkbox" || this.type == "radio" && this.checked) { - inputs.push(this.name + '=' + escape(this.value)); - } else if (this.type != "checkbox" && this.type != "radio") { - inputs.push(this.name + '=' + escape(this.value)); - } - }); - - // now if I join our inputs using '&' we'll have a query string - jQuery.post(form.action, inputs.join('&'), function(data) { - jQuery(target).html(data); - - if (callback) { - callback(); - } - - tb_init('a.thickbox, area.thickbox, input.thickbox'); - }); - - // by default - we'll always return false so it doesn't redirect the user. - return false; -} - -function update_tab(ontology,concept){ - - - jQuery.get("/tab/update/"+ontology+"/"+concept) - - -} - - -function remove_tab(link,ontology,redirect){ - - jQuery.get("/tab/remove/"+ontology,function(){ - if(redirect){ - window.location="/ontologies" - }else{ - jQuery("#tab"+ontology).remove() - - } - - }) - -} - -function selectTab(id,tab){ - nav = document.getElementById(id); - for(var x=0; x jQuery(window).width()) { - popup_list.css("left", "-250px"); + popup_list.css('left', '-250px') } } -var bp_popup_cleanup = function() { - jQuery(".bp_popup_link_container").removeClass("bp_popup_shadow"); - jQuery(".bp_popup_link").css("z-index", "").removeClass("bp_popup_link_active"); - jQuery(".bp_popup_list").hide(); +var bp_popup_cleanup = function () { + jQuery('.bp_popup_link_container').removeClass('bp_popup_shadow') + jQuery('.bp_popup_link').css('z-index', '').removeClass('bp_popup_link_active') + jQuery('.bp_popup_list').hide() } -// Sample object for working with pop-ups -/** -var filter_matched = { - init: function() { - jQuery("#filter_matched").bind("click", function(e){bp_popup_init(e)}); - jQuery(".match_filter").bind("click", function(e){filter_matched.filterMatched(e)}); - this.cleanup(); - }, - - cleanup: function() { - jQuery("html").click(bp_popup_cleanup); - jQuery(document).keyup(function(e) { - if (e.keyCode == 27) { bp_popup_cleanup(); } // esc - }); - }, - - filterMatched: function(e) { - e.preventDefault(); - e.stopPropagation(); - - var matchToFilter = jQuery(e.currentTarget); - var filterText = matchToFilter.attr("data-bp_filter_match"); - - resultsTable.fnFilter(filterText, 1); - - jQuery("#result_stats").html(jQuery(resultsTable).find("tr").length - 1 + " results"); - bp_popup_cleanup(); - } -} -**/ -jQuery(window).ready(function(){ +jQuery(window).ready(function () { // Helper text for advanced search filter - jQuery("input[type=text].help_text, textarea.help_text").each(function(){ - if (jQuery(this).val() == "") { - jQuery(this).val(jQuery(this).attr("title")); - jQuery(this).addClass("help_text_font"); + jQuery('input[type=text].help_text, textarea.help_text').each(function () { + if (jQuery(this).val() == '') { + jQuery(this).val(jQuery(this).attr('title')) + jQuery(this).addClass('help_text_font') } else { - jQuery(this).removeClass("help_text_font"); + jQuery(this).removeClass('help_text_font') } - }); + }) - jQuery("input[type=text].help_text, textarea.help_text").live("focus", (function(){ - var input = jQuery(this); - if (input.attr("title") == input.val()) { - input.val(""); - input.removeClass("help_text_font"); + jQuery('input[type=text].help_text, textarea.help_text').live('focus', (function () { + var input = jQuery(this) + if (input.attr('title') == input.val()) { + input.val('') + input.removeClass('help_text_font') } - })); + })) - jQuery("input[type=text].help_text, textarea.help_text").live("blur", (function(){ - var input = jQuery(this); - if (input.val() == "") { - input.val(input.attr("title")); - input.addClass("help_text_font"); + jQuery('input[type=text].help_text, textarea.help_text').live('blur', (function () { + var input = jQuery(this) + if (input.val() == '') { + input.val(input.attr('title')) + input.addClass('help_text_font') } - })); -}); + })) +}) // Initialize all link buttons using jQuery UI button widget -jQuery(document).ready(function(){ - jQuery("a.link_button, input.link_button").button(); +jQuery(document).ready(function () { + jQuery('a.link_button, input.link_button').button() // Properly define Chosen select width - jQuery(".chzn-container").css("width", "auto"); - jQuery(".chzn-container").css("min-width", "30em"); - jQuery("input.default").css("width", "100%"); - jQuery(".chzn-drop").css({"width": "100%"}); -}); + jQuery('.chzn-container').css('width', 'auto') + jQuery('.chzn-container').css('min-width', '30em') + jQuery('input.default').css('width', '100%') + jQuery('.chzn-drop').css({ 'width': '100%' }) +}) // Truncate more/less show and hide -jQuery(document).ready(function(){ - jQuery("a.truncated_more").live("click", function(){ - var link = jQuery(this); - link.parents("span.more_less_container").find(".truncated_more").hide(); - link.parents("span.more_less_container").find(".truncated_less").show(); +jQuery(document).ready(function () { + jQuery('a.truncated_more').live('click', function () { + var link = jQuery(this) + link.parents('span.more_less_container').find('.truncated_more').hide() + link.parents('span.more_less_container').find('.truncated_less').show() }) - jQuery("a.truncated_less").live("click", function(){ - var link = jQuery(this); - link.parents("span.more_less_container").find(".truncated_less").hide(); - link.parents("span.more_less_container").find(".truncated_more").show(); + jQuery('a.truncated_less').live('click', function () { + var link = jQuery(this) + link.parents('span.more_less_container').find('.truncated_less').hide() + link.parents('span.more_less_container').find('.truncated_more').show() }) }) -// Invoke a loading animation where dots are added to some load text -// Call this like: var loadAni = loadingAnimation("#loading"); -// Where: loading -// To kill the animation, call clearInterval(loadAni); -function loadingAnimation(loadId) { - var originalText = jQuery(loadId).text(), i = 0; - return setInterval(function() { - jQuery(loadId).append("."); - i++; - if(i == 4) { - $(loadId).html(originalText); - i = 0; - } - }, 500); -} + // Enable this to see errors in jQuery(document).ready() code // var oldReady = jQuery.ready; @@ -659,111 +160,48 @@ function loadingAnimation(loadId) { // } // }; - // Automatically get ajax content -jQuery(document).ready(function(){ +jQuery(document).ready(function () { // We do this with a delay to avoid queuing ahead of other async requests - setTimeout(getAjaxContent, 1000); -}); + setTimeout(getAjaxContent, 1000) +}) -function getAjaxContent() { +function getAjaxContent () { // Look for anchors with a get_via_ajax class and replace the parent with the resulting ajax call - $(".get_via_ajax").each(function(){ - let elem = $(this) - let parent = elem.parent() - $.get(elem.attr("href"), (conceptLabel) => { - parent.html(conceptLabel) - }) - }); - setTimeout(getAjaxContent, 500); + $('.get_via_ajax').each(function () { + let elem = $(this) + let parent = elem.parent() + $.get(elem.attr('href'), (conceptLabel) => { + parent.html(conceptLabel) + }) + }) + setTimeout(getAjaxContent, 500) } // Handle will_paginate using ajax -jQuery(".pagination a").live("click", function(e){ - var link = jQuery(this); - var replaceDiv = link.closest(".paginate_ajax"); - e.preventDefault(); +jQuery('.pagination a').live('click', function (e) { + var link = jQuery(this) + var replaceDiv = link.closest('.paginate_ajax') + e.preventDefault() if (replaceDiv.length > 0) { - replaceDiv.load(link.attr("href")); + replaceDiv.load(link.attr('href')) } else { - link.closest("div.pagination").parent().load(link.attr("href")); - } -}); - -// Facebox settings -jQuery.facebox.settings.closeImage = "<%= asset_path("facebox/closelabel.png") %>"; -jQuery.facebox.settings.loadingImage = "<%= asset_path("facebox/loading.gif") %>"; - -// Cookie handling -var BP_setCookie = function(key, value, options) { - if (typeof options === "undefined") options = {}; - var days = options["days"] || null; - var seconds = options["seconds"] || null; - var expdate = new Date(); - var expires = ""; - - if (seconds !== null) { - expdate.setSeconds(expdate.getSeconds() + seconds); - expires = " expires=" + expdate.toGMTString(); - } - - if (days !== null) { - expdate.setDate(expdate.getDate() + days); - expires = " expires=" + expdate.toGMTString(); + link.closest('div.pagination').parent().load(link.attr('href')) } +}) - document.cookie=key + "=" + value + ";" + expires; -} - -var BP_getCookies = function(){ - var pairs = document.cookie.split(";"); - var cookies = {}; - for (var i=0; i 0) - cleanPath.push(path[i]); + cleanPath.push(path[i]) } - return cleanPath; + return cleanPath } -function pageUsesTreeView() { - var usesTreeView = true; - var path = currentPathArray(); - - if (path[0] !== "ontologies") { - usesTreeView = false; - } else if ((path.length !== 2) || (path[1] === "new")) { - /* - * Some ontology pages don't use class trees, e.g.: - * ontologies/BRO/edit - * ontologies/new - */ - usesTreeView = false; - } else { - // Metadata only ontologies don't have class trees. - var metadataOnly = jQuery(document).data().bp.ont_viewer.metadata_only; - if ((metadataOnly != null) && (metadataOnly === "true")) { - usesTreeView = false; - } - } - - return usesTreeView; -} - - diff --git a/app/assets/javascripts/bp_admin.js b/app/assets/javascripts/bp_admin.js index fe05bc1cf..e6468f9f8 100644 --- a/app/assets/javascripts/bp_admin.js +++ b/app/assets/javascripts/bp_admin.js @@ -1,9 +1,3 @@ -/** - * Created by mdorf on 3/27/15. - * Updated by Syphax.Bouazzouni on 28/04/21 , users admin part - */ - - var DUMMY_ONTOLOGY = "DUMMY_ONT"; if (window.BP_CONFIG === undefined) { window.BP_CONFIG = jQuery(document).data().bp.config; @@ -203,6 +197,10 @@ AjaxAction.prototype.ajaxCall = function() { } }; +function determineHTTPS(url) { + return url.replace("http:", ('https:' == document.location.protocol ? 'https:' : 'http:')); +} + AjaxAction.prototype.onSuccessAction = function(data, ontology, deferredObj) { var self = this; if (!self.isLongOperation) { @@ -294,53 +292,6 @@ AjaxAction.prototype.act = function() { alert("AjaxAction.act is not implemented"); }; -function ResetMemcacheConnection() { - AjaxAction.call(this, "POST", "UI CACHE CONNECTION RESET", "resetcache", false); - this.setConfirmMsg(''); -} - -ResetMemcacheConnection.prototype = Object.create(AjaxAction.prototype); -ResetMemcacheConnection.prototype.constructor = ResetMemcacheConnection; - -ResetMemcacheConnection.act = function() { - new ResetMemcacheConnection().ajaxCall(); -}; - -function FlushMemcache() { - AjaxAction.call(this, "POST", "FLUSHING OF UI CACHE", "clearcache", false); - this.setConfirmMsg(''); -} - -FlushMemcache.prototype = Object.create(AjaxAction.prototype); -FlushMemcache.prototype.constructor = FlushMemcache; - -FlushMemcache.act = function() { - new FlushMemcache().ajaxCall(); -}; - -function ClearGooCache() { - AjaxAction.call(this, "POST", "FLUSHING OF GOO CACHE", "clear_goo_cache", false); - this.setConfirmMsg(''); -} - -ClearGooCache.prototype = Object.create(AjaxAction.prototype); -ClearGooCache.prototype.constructor = ClearGooCache; - -ClearGooCache.act = function() { - new ClearGooCache().ajaxCall(); -}; - -function ClearHttpCache() { - AjaxAction.call(this, "POST", "FLUSHING OF HTTP CACHE", "clear_http_cache", false); - this.setConfirmMsg(''); -} - -ClearHttpCache.prototype = Object.create(AjaxAction.prototype); -ClearHttpCache.prototype.constructor = ClearHttpCache; - -ClearHttpCache.act = function() { - new ClearHttpCache().ajaxCall(); -}; function DeleteSubmission(ontology, submissionId) { AjaxAction.call(this, "DELETE", "SUBMISSION DELETION", "ontologies/" + ontology + "/submissions/" + submissionId, false, {ontologies: ontology}); @@ -351,11 +302,6 @@ function DeleteSubmission(ontology, submissionId) { DeleteSubmission.prototype = Object.create(AjaxAction.prototype); DeleteSubmission.prototype.constructor = DeleteSubmission; -DeleteSubmission.prototype.onSuccessAction = function(data, ontology, deferredObj) { - jQuery.facebox({ - ajax: "/admin/ontologies/" + ontology + "/submissions?time=" + new Date().getTime() - }); -}; DeleteSubmission.act = function(ontology, submissionId) { new DeleteSubmission(ontology, submissionId).ajaxCall(); @@ -423,97 +369,6 @@ ProcessOntologies.act = function(action) { new ProcessOntologies(action).ajaxCall(); }; -function UpdateCheck(forceCheck) { - var params = {}; - - if (forceCheck) { - params["force_check"] = forceCheck; - } - AjaxAction.call(this, "GET", "CHECK FOR UPDATE", "update_info", false, params); - this.setProgressMessageEnabled(forceCheck); - this.setConfirmMsg(''); -} - -UpdateCheck.prototype = Object.create(AjaxAction.prototype); -UpdateCheck.prototype.constructor = UpdateCheck; - -UpdateCheck.prototype.onSuccessAction = function(data, ontology, deferredObj) { - var self = this; - delete data["success"]; - updateInfo = data["update_info"]; - - if (updateInfo["update_check_enabled"]) { - var lastCheck = ""; - - if (updateInfo["date_checked"]) { - var updateDate = parseReportDate(updateInfo["date_checked"]); - - if (updateDate) { - lastCheck = "Last update check on: " + updateDate; - } - } - - if (updateInfo["update_available"]) { - // update found - show in all cases - data["notices"] = "Update available. You are running the version: " + updateInfo["current_version"] + ". Updated version: " + updateInfo["update_version"] + "."; - - if (lastCheck) { - data["notices"] += " " + lastCheck + "."; - } - - if (updateInfo["notes"]) { - data["notices"] += " Update notes: " + updateInfo["notes"]; - } - } else if (self.params && self.params["force_check"]) { - // no update found, but user was checking explicitly - show message - data["notices"] = "No update available. You are running the latest version: " + updateInfo["current_version"] + "."; - - if (lastCheck) { - data["notices"] += " " + lastCheck + "."; - } - } else { - // no update found, and user wasn't checking, - // just a default check on page load - show nothing - delete data["notices"]; - } - - if (updateInfo.hasOwnProperty("appliance_id")) { - jQuery("#appliance-id span").text(updateInfo["appliance_id"]) - } - } -}; - -UpdateCheck.prototype.onErrorAction = function(errors) { - var self = this; - - // hide errors unless user explicitly requested an update check - if (!self.params || !self.params["force_check"]) { - errors.length = 0; - } -}; - -UpdateCheck.isUpdateCheckEnabled = false; -UpdateCheck.act = function(forceCheck) { - if (UpdateCheck.isUpdateCheckEnabled) { - new UpdateCheck(forceCheck).ajaxCall(); - } else { - jQuery.ajax({ - url: "/admin/update_check_enabled", - success: function (data) { - if (data) { - jQuery("#update_check").show(); - jQuery("#site-admin-appliance-id").show(); - jQuery("#site-admin-update-check").show(); - new UpdateCheck(forceCheck).ajaxCall(); - UpdateCheck.isUpdateCheckEnabled = true; - } - }, - error: function () { - console.log("An error occurred while performing a check for the latest version."); - } - }); - } -}; // end: individual actions classes ----------------------- @@ -554,7 +409,9 @@ function populateOntologyRows(data) { bpLinks += "Log  |  "; } bpLinks += "REST  |  "; - bpLinks += "Submissions"; + let title = `Ontology Submissions for ${acronym}` + let link = `Submissions` + bpLinks += link var errStatus = ontology["errErrorStatus"] ? ontology["errErrorStatus"].join(", ") : ''; var missingStatus = ontology["errMissingStatus"] ? ontology["errMissingStatus"].join(", ") : ''; @@ -663,8 +520,6 @@ function displayOntologies(data, ontology) { _showStatusMessages([], [json.errors], [], false); } setDateGenerated(json); - // Keep header at top of table even when scrolling - // new jQuery.fn.dataTable.FixedHeader(ontTable); }, "columnDefs": [ { @@ -748,10 +603,7 @@ function displayOntologies(data, ontology) { return ontTable; } -function showSubmissions(ev, acronym) { - ev.preventDefault(); - jQuery.facebox({ ajax: "/admin/ontologies/" + acronym + "/submissions" }); -} + function showOntologiesToggleLinks(problemOnly) { var str = 'View Ontologies:    '; @@ -767,27 +619,7 @@ function showOntologiesToggleLinks(problemOnly) { jQuery(".admin.index").ready(function() { // display ontologies table on load displayOntologies({}, DUMMY_ONTOLOGY); - displayUsers({}); - UpdateCheck.act(); - - // make sure facebox window is empty before populating it - // otherwise ajax requests stack up and you see more than - // one ontology's submissions - jQuery(document).bind('beforeReveal.facebox', function() { - jQuery("#facebox .content").empty(); - }); - - // remove hidden divs for submissions of previously - // clicked ontologies - jQuery(document).bind('reveal.facebox', function() { - jQuery('div[id=facebox]:hidden').remove(); - }); - // convert facebox window into a modal mode - jQuery(document).bind('loading.facebox', function() { - jQuery(document).unbind('keydown.facebox'); - jQuery('#facebox_overlay').unbind('click'); - }); jQuery("div.ontology_nav").html('' + showOntologiesToggleLinks(problemOnly) + 'Apply to Selected Rows:        Go'); @@ -822,9 +654,6 @@ jQuery(".admin.index").ready(function() { } }); - jQuery('#adminUsers').on('click', '.delete-user', function(event) { - DeleteUsers.act(this.dataset.accountName); - }); // BUTTON onclick actions --------------------------------------- @@ -834,726 +663,18 @@ jQuery(".admin.index").ready(function() { performActionOnOntologies(); }); - // onclick action for "Flush UI Cache" button - jQuery("#flush_memcache_action").click(function() { - FlushMemcache.act(); - }); - - // onclick action for "Reset Memcache Connection" button - jQuery("#reset_memcache_connection_action").click(function() { - ResetMemcacheConnection.act(); - }); - - // onclick action for "Flush Goo Cache" button - jQuery("#flush_goo_cache_action").click(function() { - ClearGooCache.act(); - }); - - // onclick action for "Flush HTTP Cache" button - jQuery("#flush_http_cache_action").click(function() { - ClearHttpCache.act(); - }); - // onclick action for "Refresh Report" button - jQuery("#refresh_report_action").click(function() { + jQuery("#refresh_report_action").click(function() { RefreshReport.act(); }); - // onclick action for "Update Check" button - jQuery("#update_check_action").click(function() { - UpdateCheck.act(true); - }); - - // end: BUTTON onclick actions ----------------------------------- - //============================================================== - // GROUPS MANAGEMENT - //============================================================== - displayGroups({}); - - // allow selecting of rows, except on link clicks - jQuery('#adminGroups tbody').on('click', 'tr', function(event) { - if (event.target.tagName.toLowerCase() != 'a') { - jQuery(this).toggleClass('selected'); - } - }); - - jQuery("div.groups_nav").html(` - - - New - - - Apply to Selected Rows:     - -      - - Go - - `); - - jQuery('#group_admin_action_submit').on('click', function(event) { - var action = jQuery('#group_admin_action').val(); - - if (!action) { - alertify.alert("Please choose an action to perform on the selected groups."); - return; - } - - switch(action) { - case "delete": - DeleteGroups.act(); - break; - } - }); - - jQuery('#group_new_action').on('click', function (event) { - jQuery.facebox({ - ajax: "/admin/groups/new?time=" + new Date().getTime() - }); - }); - - jQuery('#adminGroups').on('click', 'a.edit-group', function(event) { - jQuery.facebox({ - ajax: "/admin/groups/" + encodeURIComponent(event.target.dataset.groupName) + "/edit?time=" + new Date().getTime() - }); - }); - - - //============================================================== - // CATEGORIES MANAGEMENT - //============================================================== - displayCategories({}); - // allow selecting of rows, except on link clicks - jQuery('#adminCategories tbody').on('click', 'tr', function(event) { - if (event.target.tagName.toLowerCase() != 'a') { - jQuery(this).toggleClass('selected'); - } - }); - - jQuery("div.categories_nav").html(` - - - New - - - Apply to Selected Rows:     - -      - - Go - - `); - - jQuery('#category_admin_action_submit').on('click', function(event) { - var action = jQuery('#category_admin_action').val(); - - if (!action) { - alertify.alert("Please choose an action to perform on the selected categories."); - return; - } - - switch(action) { - case "delete": - DeleteCategories.act(); - break; - } - }); + // end: BUTTON onclick actions ----------------------------------- - jQuery('#category_new_action').on('click', function (event) { - jQuery.facebox({ - ajax: "/admin/categories/new?time=" + new Date().getTime() - }); - }); - jQuery('#adminCategories').on('click', 'a.edit-category', function(event) { - jQuery.facebox({ - ajax: "/admin/categories/" + encodeURIComponent(event.target.dataset.categoryName) + "/edit?time=" + new Date().getTime() - }); - }); //============================================================== // MANAGEMENT COMMONS //============================================================== - - jQuery(document).on("click", "#facebox a.dismiss-dialog", function (event) { - jQuery(document).trigger('close.facebox'); - }); - - jQuery(document).on('ajax:success', "#facebox form.admin-collection-form", (event, response, status, xhr) => { - jQuery(document).trigger('close.facebox'); - if (response && response.success) { - _showStatusMessages([response.success], [], [], false); - } - refreshCollection(event.target.dataset.collection); - }); - jQuery(document).on('ajax:error', "#facebox form.admin-collection-form", (event, xhr, status, error) => { - if (xhr.responseJSON) { - displayDialogErrorMessages(xhr.responseJSON) - } else { - displayDialogErrorMessages(status); - } - }); - - function refreshCollection(collectionName) { - switch (collectionName) { - case "groups": - displayGroups({}); - break; - case "categories": - displayCategories({}); - break; - default: - alertify.alert("Unable to refresh unknown collection '" + collectionName + "'"); - } - } - - function displayDialogErrorMessages(data, settings) { - settings ||= {} - - let append = settings.append || false; - - let errorListNode = jQuery("#facebox .alert-box ul"); - - if (!append) { - errorListNode.empty(); - } - - let messages = []; - if (typeof data == "string" || data instanceof String) { - messages.push(data) - } - if (typeof data == "object" && data.errors) { - messages.push.apply(messages, Object.values(data.errors)); - } - if (typeof data == "object" && data.status && data.status / 200 != 1) { - messages.push("Request error: " + data.statusText); - } - - for (let msg of messages) { - errorListNode.append(jQuery("
  • ").text(msg)) - } - - if (messages.length == 0) { - errorListNode.parents(".alert-box").hide(); - } else { - errorListNode.parents(".alert-box").show(); - } - } }); - - -/* users part */ -function populateUserRows(data) { - let users = data['users']; - let allRows = []; - // let hideFields = ["format", "administeredBy", "date_created", "report_date_updated", "errErrorStatus", "errMissingStatus", "problem", "logFilePath"]; - users.forEach(user =>{ - let id = '' + user['@id'] + ''; - let email = user['email']; - let username = user['username']; - let roles = user['role']; - let firstname = user['firstName'] - let lastname = user['lastName'] - let created = user['created'] - let actions = [ - 'Detail' , - 'Delete', - 'Login as', - - ] - let row = [firstname, lastname, username, email , roles , id , created , actions.join('|')]; - allRows.push(row); - }) - - return allRows; -} - -function displayUsers(data) { - let ontTable = null; - let allRows - if (jQuery.fn.dataTable.isDataTable('#adminUsers')) { - ontTable = jQuery('#adminUsers').DataTable(); - - if (ontology === DUMMY_ONTOLOGY) { - // refreshing entire table - allRows = populateUserRows(data); - ontTable.clear(); - ontTable.rows.add(allRows); - ontTable.draw(); - } else { - // refreshing individual row - } - } else { - ontTable = jQuery("#adminUsers").DataTable({ - "ajax": { - "url": "/admin/users", - "contentType": "application/json", - "dataSrc": function (json) { - return populateUserRows(json); - } - }, - "rowCallback": function(row, data, index) { - var acronym = jQuery('td:nth-child(3)', row).text(); - - jQuery(row).attr("id", "tr_" + acronym); - if (data[data.length - 1] === true) { - jQuery(row).addClass("problem"); - } - }, - "initComplete": function(settings, json) { - }, - "columnDefs": [ - { - "targets": 0, - "searchable": true, - "title": "First Name", - }, - { - "targets": 1, - "searchable": true, - "title": "Last Name", - }, - { - "targets": 2, - "searchable": true, - "title": "Username", - }, - { - "targets": 3, - "searchable": true, - "title": "Email", - }, - { - "targets": 4, - "searchable": true, - "title": "Roles", - }, - { - "targets": 5, - "searchable": true, - "orderable": false, - "title": "Id", - }, - { - "targets": 6, - "searchable": true, - "orderable": true, - "title": "Created at", - }, - { - "targets": 7, - "searchable": false, - "orderable": false, - "title": "Actions", - "width": "210px" - } - ], - "autoWidth": false, - "lengthChange": false, - "searching": true, - "language": { - "search": "Filter: ", - "emptyTable": "No users available" - }, - "info": true, - "paging": true, - "pageLength": 100, - "ordering": true, - "responsive": true, - "stripeClasses": ["", "alt"], - }); - } - return ontTable; -} - -function DeleteUsers(user) { - AjaxAction.call(this, "DELETE", "USERS DELETION", "accounts/"+user, false); - this.setConfirmMsg('Permanently delete "' + user +'"?'); -} - -DeleteUsers.prototype = Object.create(AjaxAction.prototype); -DeleteUsers.prototype.constructor = DeleteUsers; -DeleteUsers.prototype.onSuccessAction = function(username) { - let ontTable = jQuery('#adminUsers').DataTable(); - ontTable.row(jQuery("#tr_" + username)).remove().draw(); -}; - -DeleteUsers.prototype._ajaxCall = function (username) { - let errors = []; - let success = []; - let notices = []; - jQuery.ajax({ - method: 'DELETE', - url: 'accounts/'+username, - data: [], - dataType: "json", - success: (data, msg) => { - success.push('"' + username + '" user successfully deleted') - this.onSuccessAction(username) - _showStatusMessages(success, errors, notices, false); - }, - error: function(request, textStatus, errorThrown) { - console.log('error') - errorState = true; - errors.push(request.status + ": " + errorThrown); - _showStatusMessages(success, errors, notices, false); - }, - complete: function(request, textStatus) { - - } - }); - -} - -DeleteUsers.prototype.ajaxCall = function (username){ - alertify.confirm(this.confirmMsg, (e) => { - if (e) { - this._ajaxCall(username); - } - }); -} -DeleteUsers.act = function(user) { - new DeleteUsers(user).ajaxCall(user); -}; - -/* groups part */ -function displayGroups(data, group) { - let ontTable = null; - let allRows - if (jQuery.fn.dataTable.isDataTable('#adminGroups')) { - ontTable = jQuery('#adminGroups').DataTable().ajax.reload(); - } else { - ontTable = jQuery("#adminGroups").DataTable({ - "ajax": { - "url": "/admin/groups", - "contentType": "application/json", - "dataSrc": function (json) { - return populateGroupRows(json); - } - }, - "rowCallback": function(row, data, index) { - var acronym = jQuery('td:nth-child(4)', row).text(); - - jQuery(row).attr("id", "tr_" + acronym); - }, - "initComplete": function(settings, json) { - }, - "columnDefs": [ - { - "targets": 0, - "searchable": true, - "title": "Name", - }, - { - "targets": 1, - "searchable": true, - "title": "Description", - }, - { - "targets": 2, - "searchable": true, - "title": "Created", - }, - { - "targets": 3, - "searchable": true, - "title": "Id", - }, - { - "targets": 4, - "searchable": false, - "orderable": false, - "title": "Count", - }, - { - "targets": 5, - "searchable": false, - "orderable": false, - "title": "Actions", - "width": "200px" - } - ], - "autoWidth": false, - "lengthChange": false, - "searching": true, - "language": { - "search": "Filter: ", - "emptyTable": "No groups available" - }, - "info": true, - "paging": true, - "pageLength": 100, - "ordering": true, - "responsive": true, - "dom": '<"groups_nav"><"top"fi>rtip', - "stripeClasses": ["", "alt"], - }); - } - return ontTable; -} - -function populateGroupRows(data) { - let groups = data['groups']; - let allRows = groups.map(group => { - let name = group['name']; - let description = group['description'] - let created = group['created']; - let id = group['acronym']; - let nb = [ - ' ' + group['ontologies'].length + ' ', - ]; - let actions = [ - 'Edit', - ] - return [name, description, created, id , nb, actions.join('|')]; - }) - - return allRows; -} - -function DeleteGroups() { -} - -DeleteGroups.act = function(groupName) { - let group2delete = jQuery("#adminGroups tr.selected td:nth-child(4)").map(function(index, value) { return value.textContent.trim();}).toArray(); - let confirmMsg = "You are about to delete the following groups:
    " + group2delete.join(",") + "

    Should I proceed?"; - alertify.confirm(confirmMsg, (e) => { - if (e) { - _clearStatusMessages(); - let success = []; - let errors = []; - let notices = []; - let errorState = false; - let deferredObj = jQuery.Deferred(); - let initialDeferredObj = deferredObj; - - for (let group of group2delete) { - fun = () => { return jQuery.ajax("/admin/groups/" + encodeURIComponent(group), { - method: "DELETE", - dataType: "json", - success: function(data, msg) { - var reg = /\s*,\s*/g; - - if (data.errors) { - errorState = true; - errors.push.apply(errors, data.errors); - } - - if (data.success) { - success.push(data.success); - } - - if (data.notices) { - notices.push.apply(notices, data.notices); - } - - _showStatusMessages(success, errors, notices, false); - }, - error: function(request, textStatus, errorThrown) { - errorState = true; - errors.push(request.status + ": " + errorThrown); - _showStatusMessages(success, errors, notices, false); - }, - complete: function(request, textStatus) { - if (errorState) { - jQuery("#tr_" + group).removeClass('selected'); - } - } - }) - }; - deferredObj = deferredObj.then(fun, fun); - } - // hide progress message and deselect rows after ALL operations have completed - deferredObj.always(function () { - jQuery("#adminGroups").DataTable().ajax.reload(); - }); - - initialDeferredObj.resolve(); - } - }); -} -/* categories part */ -function displayCategories(data, category) { - let ontTable = null; - let allRows - if (jQuery.fn.dataTable.isDataTable('#adminCategories')) { - ontTable = jQuery('#adminCategories').DataTable().ajax.reload(); - } else { - ontTable = jQuery("#adminCategories").DataTable({ - "ajax": { - "url": "/admin/categories", - "contentType": "application/json", - "dataSrc": function (json) { - return populateCategoryRows(json); - } - }, - "rowCallback": function(row, data, index) { - var acronym = jQuery('td:nth-child(4)', row).text(); - - jQuery(row).attr("id", "tr_" + acronym); - }, - "initComplete": function(settings, json) { - }, - "columnDefs": [ - { - "targets": 0, - "searchable": true, - "title": "Name", - }, - { - "targets": 1, - "searchable": true, - "title": "Description", - }, - { - "targets": 2, - "searchable": true, - "title": "Created", - }, - { - "targets": 3, - "searchable": true, - "title": "Id", - }, - { - "targets": 4, - "searchable": false, - "orderable": false, - "title": "Parent", - }, - { - "targets": 5, - "searchable": false, - "orderable": false, - "title": "Count", - }, - { - "targets": 6, - "searchable": false, - "orderable": false, - "title": "Actions", - "width": "250px" - } - ], - "autoWidth": false, - "lengthChange": false, - "searching": true, - "language": { - "search": "Filter: ", - "emptyTable": "No categories available" - }, - "info": true, - "paging": true, - "pageLength": 100, - "ordering": true, - "responsive": true, - "dom": '<"categories_nav"><"top"fi>rtip', - "stripeClasses": ["", "alt"], - }); - } - return ontTable; -} - -function populateCategoryRows(data) { - let categories = data['categories']; - let allRows = categories.map(category => { - let name = category['name']; - let description = category['description'] - let created = category['created']; - let id = category['acronym']; - let parentCategory = category['parentCategory']; - let nb = [ - ' ' + category['ontologies'].length + ' ', - ]; - let actions = [ - 'Edit', - ] - return [name, description, created, id , parentCategory, nb , actions.join('|')]; - }) - - return allRows; -} - -function DeleteCategories() { -} - -DeleteCategories.act = function(groupName) { - let category2delete = jQuery("#adminCategories tr.selected td:nth-child(4)").map(function(index, value) { return value.textContent.trim();}).toArray(); - let confirmMsg = "You are about to delete the following categories:
    " + category2delete.join(",") + "

    Should I proceed?"; - alertify.confirm(confirmMsg, (e) => { - if (e) { - _clearStatusMessages(); - let success = []; - let errors = []; - let notices = []; - let errorState = false; - let deferredObj = jQuery.Deferred(); - let initialDeferredObj = deferredObj; - - for (let category of category2delete) { - fun = () => { return jQuery.ajax("/admin/categories/" + encodeURIComponent(category), { - method: "DELETE", - dataType: "json", - success: function(data, msg) { - var reg = /\s*,\s*/g; - - if (data.errors) { - errorState = true; - errors.push.apply(errors, data.errors); - } - - if (data.success) { - success.push(data.success); - } - - if (data.notices) { - notices.push.apply(notices, data.notices); - } - - _showStatusMessages(success, errors, notices, false); - }, - error: function(request, textStatus, errorThrown) { - errorState = true; - errors.push(request.status + ": " + errorThrown); - _showStatusMessages(success, errors, notices, false); - }, - complete: function(request, textStatus) { - if (errorState) { - jQuery("#tr_" + category).removeClass('selected'); - } - } - }) - }; - deferredObj = deferredObj.then(fun, fun); - } - // hide progress message and deselect rows after ALL operations have completed - deferredObj.always(function () { - jQuery("#adminCategories").DataTable().ajax.reload(); - }); - - initialDeferredObj.resolve(); - } - }); -} - -/***************************** - * COMMON FUNCTIONS - *****************************/ -function _clearStatusMessages() { - jQuery("#progress_message").hide(); - jQuery("#success_message").hide(); - jQuery("#error_message").hide(); - jQuery("#info_message").hide(); - jQuery("#progress_message").html(""); - jQuery("#success_message").html(""); - jQuery("#error_message").html(""); - jQuery("#info_message").html(""); -} \ No newline at end of file diff --git a/app/assets/javascripts/bp_ajax_controller.js b/app/assets/javascripts/bp_ajax_controller.js index 97387071e..a67e66550 100644 --- a/app/assets/javascripts/bp_ajax_controller.js +++ b/app/assets/javascripts/bp_ajax_controller.js @@ -110,29 +110,6 @@ var ajax_process_ont = function() { }; -// ************************************************************************************** -// CLASS LABELS - -// Note: If we don't query every time, using the array should be faster; it -// means the ajax_process_init must be called after all the elements -// are created because they will not be detected in a dynamic iteration. -var ajax_cls_array = []; - -var ajax_process_cls_init = function() { - ajax_cls_array = jQuery("a.cls4ajax").toArray(); -}; - -var ajax_process_cls_halt = function () { - ajax_cls_array = []; - window.clearInterval(ajax_process_cls_interval); // stop the ajax process - // Note: might leave faulty href links, but it usually means moving on to entirely different content - // so it's not likely those links will be available for interaction. - // clear all the classes and ontologies to be resolved by ajax - //jQuery("a.cls4ajax").removeClass('cls4ajax'); - //jQuery("a.ajax-modified-cls").removeClass('ajax-modified-cls'); -}; - - // ************************************************************************************** // INTERPORTAL CLASS LABELS diff --git a/app/assets/javascripts/bp_analytics.js b/app/assets/javascripts/bp_analytics.js deleted file mode 100644 index 701a03f26..000000000 --- a/app/assets/javascripts/bp_analytics.js +++ /dev/null @@ -1,82 +0,0 @@ -jQuery(document).ready(function(){ - new SearchAnalytics().bindTracker(); -}); - -function Analytics() { - this.track = function(segment, analytics_action, params, callback) { - params["segment"] = segment; - params["analytics_action"] = analytics_action; - jQuery.ajax({ - url: "/analytics", - data: params, - type: "POST", - timeout: 100, - success: function(){ - if (typeof callback === "function") callback(); - }, - error: function(){ - if (typeof callback === "function") callback(); - } - }); - }; -} - -function SearchAnalytics() { - this.bindTracker = function() { - jQuery(document).on("click", "#search_results_container div.class_link a", function(e) { - e.preventDefault(); - - var link; - if (e.target.nodeName == "SPAN") { - link = jQuery(e.target.parentElement); - } else { - link = jQuery(e.target); - } - - var href = link.attr("href"); - var params = new SearchAnalytics().linkInformation(link); - new Analytics().track("search", "result_clicked", params, function(){ - window.location.href = href; - }); - }); - }; - - this.linkInformation = function(link) { - var info = {}, resultsIndex = 0; - var ontologyPosition = jQuery("#search_results div.search_result").index(jQuery(link).closest(".search_result")) + 1; - - info.ontology_clicked = link.closest(".search_result").data("bp_ont_id"); - - // Find out the position of the search result in the list - if (link.closest(".additional_results").length === 0) { - info.position = ontologyPosition; - } else { - info.position = link.closest(".additional_results").children(".search_result_additional").index(link.closest(".search_result_additional")) + 1; - } - - // Was this an additional result or a top-level - info.additional_result = link.closest(".additional_results").length > 0; - - // Get the name of ontologies higher in the list - if (info.position > 1 || info.additional_result === true) { - var results = jQuery("#search_results div.search_result"); - info.higher_ontologies = []; - while (resultsIndex < ontologyPosition - 1) { - info.higher_ontologies.push(jQuery(results[resultsIndex]).data("bp_ont_id")); - resultsIndex += 1; - } - } - - // Concept id - info.concept_id = link.data("bp_conceptid"); - - // Search query - info.query = jQuery("#search_keywords").val(); - - // Exact match - info.exact_match = link.data("exact_match"); - - return info; - }; -} - diff --git a/app/assets/javascripts/bp_annotator.js b/app/assets/javascripts/bp_annotator.js index 6f14c66a4..cb1a3d858 100644 --- a/app/assets/javascripts/bp_annotator.js +++ b/app/assets/javascripts/bp_annotator.js @@ -509,21 +509,25 @@ function generateParameters() { jQuery(document).ready(function() { "use strict"; jQuery("#annotator_button").click(get_annotations); - - jQuery("#semantic_types").select2({ - allowClear: true, - dropdownParent: jQuery(".annotator form") + + jQuery("#semantic_types").chosen({ + allow_single_deselect: true, + width: '100%', // Adjust the width as needed + dropdown_parent: jQuery(".annotator form") }); - jQuery("#semantic_groups").select2({ - allowClear: true, - dropdownParent: jQuery(".annotator form") + jQuery("#semantic_groups").chosen({ + allow_single_deselect: true, + width: '100%', // Adjust the width as needed + dropdown_parent: jQuery(".annotator form") }); - jQuery("#ontology_ontologyId").select2({ - allowClear: true, - dropdownParent: jQuery(".annotator form") + jQuery("#ontology_ontologyId").chosen({ + allow_single_deselect: true, + width: '100%', // Adjust the width as needed + dropdown_parent: jQuery(".annotator form") }); + jQuery("#insert_text_link").click(insertSampleText); jQuery("#advancedOptionsLink").click(toggle_advanced_options); diff --git a/app/assets/javascripts/bp_annotatorplus.js b/app/assets/javascripts/bp_annotatorplus.js index bc051caa9..2ee9a78d3 100644 --- a/app/assets/javascripts/bp_annotatorplus.js +++ b/app/assets/javascripts/bp_annotatorplus.js @@ -496,22 +496,27 @@ function generateParameters() { jQuery(document).ready(function() { "use strict"; jQuery("#annotator_button").click(get_annotations); - - jQuery("#semantic_types").select2({ - allowClear: true, - dropdownParent: jQuery(".annotatorplus form") + + + jQuery("#semantic_types").chosen({ + allow_single_deselect: true, + width: '100%', // Adjust the width as needed + dropdown_parent: jQuery(".annotatorplus form") }); - jQuery("#semantic_groups").select2({ - allowClear: true, - dropdownParent: jQuery(".annotatorplus form") + jQuery("#semantic_groups").chosen({ + allow_single_deselect: true, + width: '100%', // Adjust the width as needed + dropdown_parent: jQuery(".annotatorplus form") }); - jQuery("#ontology_ontologyId").select2({ - allowClear: true, - dropdownParent: jQuery(".annotatorplus form") + jQuery("#ontology_ontologyId").chosen({ + allow_single_deselect: true, + width: '100%', // Adjust the width as needed + dropdown_parent: jQuery(".annotatorplus form") }); + jQuery("#insert_text_link").click(insertSampleText); jQuery("#advancedOptionsLink").click(toggle_advanced_options); diff --git a/app/assets/javascripts/bp_property_tree.js b/app/assets/javascripts/bp_property_tree.js deleted file mode 100644 index 5eb3c2780..000000000 --- a/app/assets/javascripts/bp_property_tree.js +++ /dev/null @@ -1,334 +0,0 @@ -/* -* jQuery SimpleTree Drag&Drop plugin -* Update on 22th May 2008 -* Version 0.3 -* -* Licensed under BSD -* Copyright (c) 2008, Peter Panov , IKEEN Group http://www.ikeen.com -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* * Redistributions in binary form must reproduce the above copyright -* notice, this list of conditions and the following disclaimer in the -* documentation and/or other materials provided with the distribution. -* * Neither the name of the Peter Panov, IKEEN Group nor the -* names of its contributors may be used to endorse or promote products -* derived from this software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY Peter Panov, IKEEN Group ``AS IS'' AND ANY -* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -* DISCLAIMED. IN NO EVENT SHALL Peter Panov, IKEEN Group BE LIABLE FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -(function($) { - var NCBOPropertyTree = function(element, opt) { - var obj = this; - var OPTIONS; - var ROOT_ID = "roots"; - - OPTIONS = { - autoclose: false, - beforeExpand: false, - afterExpand: false, - afterExpandError: false, - afterSelect: false, - afterJumpToClass: false, - timeout: 999999, - treeClass: "ncboTree", - width: 350, - ncboUIURL: "http://bioportal.bioontology.org", - apikey: null, - ontology: null - }; - - OPTIONS = $.extend(OPTIONS, opt); - - // Required options - if (OPTIONS.ontology == null) - throw new Error("You must provide an ontology id for NCBO Property Tree Widget to operate"); - - var $TREE_CONTAINER = element; - var TREE = $("
      ").append($("
    • ").addClass("root")); - var ROOT = $('.root', TREE); - var mousePressed = false; - TREE.css("width", OPTIONS.width); - - // Empty out the tree container - $TREE_CONTAINER.html(""); - - // Add the actual tree - $TREE_CONTAINER.append(TREE); - - // Add provided class - TREE.addClass(OPTIONS.treeClass); - - // format the nodes to match what simpleTree is expecting - this.formatNodes = function(nodes) { - var holder = $(""); - var ul = $("
        "); - - // Sort by prefLabel - nodes.sort(function(a, b){ - var aName = a.prefLabel.toLowerCase(); - var bName = b.prefLabel.toLowerCase(); - return ((aName < bName) ? -1 : ((aName > bName) ? 1 : 0)); - }); - - $.each(nodes, function(index, node){ - var li = $("
      • "); - var a = $("").attr("href", obj.determineHTTPS(node["@id"])).html(node.prefLabel); - a.attr("data-id", encodeURIComponent(node.id)) - .attr("data-label", node.label) - .attr("data-definition", node.definition) - .attr("data-parents", node.parents) - .attr("data-prefLabel", node.prefLabel); - - ul.append(li.append(a)); - - var hasChildrenNotExpanded = typeof node.children !== 'undefined' && node.hasChildren && node.children.length == 0; - if (node.hasChildren && typeof node.children === 'undefined' || hasChildrenNotExpanded) { - var ajax_ul = $("
          ").addClass("ajax"); - var ajax_li = $("
        • "); - var ajax_a = $("").attr("href", node.links.children); - li.append(ajax_ul.append(ajax_li.append(ajax_a))); - } else if (typeof node.children !== 'undefined' && node.children.length > 0) { - var child_ul = obj.formatNodes(node.children); - li.append(child_ul); - } - }); - - holder.append(ul) - return holder.html(); - } - - this.selectClass = function(cls){ - var foundClass = $(TREE.find("a[data-id='" + cls + "']")); - $(TREE.find("a.active")[0]).removeClass("active"); - foundClass.addClass("active"); - } - - this.selectedClass = function(){ - var cls = $(TREE.find("a.active")[0]); - if (cls.length == 0) { - return null; - } else { - return { - id: decodeURIComponent(cls.data("id")), - prefLabel: cls.html(), - URL: cls.attr("href") - }; - } - } - - this.closeNearby = function(obj) { - $(obj).siblings().filter('.folder-open, .folder-open-last').each(function(){ - var childUl = $('>ul',this); - var className = this.className; - this.className = className.replace('open', 'close'); - childUl.hide(); - }); - }; - - this.nodeToggle = function(obj) { - var childUl = $('>ul',obj); - if (childUl.is(':visible')) { - obj.className = obj.className.replace('open','close'); - childUl.hide(); - } else { - obj.className = obj.className.replace('close','open'); - childUl.show(); - if (OPTIONS.autoclose) - obj.closeNearby(obj); - if (childUl.is('.ajax')) - obj.setAjaxNodes(childUl, obj.id); - } - }; - - this.setAjaxNodes = function(node, parentId, successCallback, errorCallback) { - if (typeof OPTIONS.beforeExpand == 'function') { - OPTIONS.beforeExpand(node); - } - $TREE_CONTAINER.trigger("beforeExpand", node); - - var url = $.trim($('a', node).attr("href")); - if (url) { - $.ajax({ - type: "GET", - url: url, - data: { - apikey: OPTIONS.apikey, - include: "prefLabel,hasChildren", - no_context: true - }, - crossDomain: true, - contentType: 'application/json', - timeout: OPTIONS.timeout, - success: function(response) { - var nodes = obj.formatNodes(response.collection) - node.removeAttr('class'); - node.html(nodes); - $.extend(node, {url:url}); - obj.setTreeNodes(node, true); - if (typeof OPTIONS.afterExpand == 'function') { - OPTIONS.afterExpand(node); - } - $TREE_CONTAINER.trigger("afterExpand", node); - if (typeof successCallback == 'function') { - successCallback(node); - } - }, - error: function(response) { - if (typeof OPTIONS.afterExpandError == 'function') { - OPTIONS.afterExpandError(node); - } - if (typeof errorCallback == 'function') { - errorCallback(node); - } - $TREE_CONTAINER.trigger("afterExpandError", node); - } - }); - } - }; - - this.setTreeNodes = function(target, useParent) { - target = useParent ? target.parent() : target; - $('li>a', target).addClass('text').bind('selectstart', function() { - return false; - }).click(function(){ - var parent = $(this).parent(); - var selectedNode = $(this); - $('.active', TREE).attr('class', 'text'); - if (this.className == 'text') { - this.className = 'active'; - } - if (typeof OPTIONS.afterSelect == 'function') { - OPTIONS.afterSelect(decodeURIComponent(selectedNode.data("id")), selectedNode.text(), selectedNode); - } - $TREE_CONTAINER.trigger("afterSelect", [decodeURIComponent(selectedNode.data("id")), selectedNode.text(), selectedNode]); - return false; - }).bind("contextmenu",function(){ - $('.active', TREE).attr('class', 'text'); - if (this.className == 'text') { - this.className = 'active'; - } - if (typeof OPTIONS.afterContextMenu == 'function') { - OPTIONS.afterContextMenu(parent); - } - return false; - }).mousedown(function(event) { - mousePressed = true; - cloneNode = $(this).parent().clone(); - var LI = $(this).parent(); - return false; - }); - - $('li', target).each(function(i) { - var className = this.className; - var open = false; - var cloneNode=false; - var LI = this; - var childNode = $('>ul',this); - if (childNode.size() > 0){ - var setClassName = 'folder-'; - if (className && className.indexOf('open') >= 0) { - setClassName = setClassName + 'open'; - open = true; - } else { - setClassName = setClassName+'close'; - } - this.className = setClassName + ($(this).is(':last-child') ? '-last' : ''); - - if (!open || className.indexOf('ajax') >= 0) - childNode.hide(); - - obj.setTrigger(this); - } else { - var setClassName = 'doc'; - this.className = setClassName + ($(this).is(':last-child') ? '-last' : ''); - } - }).before('
        •  
        • ') - .filter(':last-child') - .after('
        • '); - }; - - this.setTrigger = function(node) { - $('>a',node).before(''); - var trigger = $('>.trigger', node); - trigger.click(function(event){ - obj.nodeToggle(node); - }); - // TODO: $.browser was removed in jQuery 1.9, check IE compatability - // if (!$.browser.msie) { - // trigger.css('float','left'); - // } - }; - - this.determineHTTPS = function(url) { - if (typeof url === 'undefined') { return url; } - return url.replace("http:", ('https:' == document.location.protocol ? 'https:' : 'http:')); - } - - // Populate roots and init tree - this.init = function() { - ROOT.html($("").html("Loading...").css("font-size", "smaller")); - $.ajax({ - url: obj.determineHTTPS(OPTIONS.ncboUIURL) + "/ajax/properties/tree", - data: { - apikey: OPTIONS.apikey, - ontology: OPTIONS.ontology, - no_context: true - }, - contentType: 'application/json', - crossDomain: true, - success: function(roots) { - if (roots.length > 0) { - // Flatten potentially nested arrays - roots = $.map([roots], function(n){ - return n; - }); - ROOT.html(obj.formatNodes(roots)); - obj.setTreeNodes(ROOT, false); - } else { - ROOT.html("No properties exist for this ontology"); - ROOT.css("font-size", "14px").css("margin", "5px"); - jQuery("#prop_contents .spinner-border").hide() - } - - if (typeof OPTIONS.onInit === 'function') { OPTIONS.onInit(); } - }, - error: function(jqXHR, textStatus, errorThrown) { - console.log(`NCBOPropertyTree error: ${textStatus} : ${errorThrown}`); - ROOT.html($("").html(`Problem retrieving properties: ${errorThrown}`).css("font-size", "smaller")); - } - }); - }; - } - - $.fn.NCBOPropertyTree = function(options) { - // Returns the original object(s) so they can be chained - return this.each(function() { - var $this = $(this); - - // Return early if this element already has a plugin instance - if ($this.data('NCBOPropertyTree')) return; - - // pass options to plugin constructor - var ncboPropertyTree = new NCBOPropertyTree($this, options); - ncboPropertyTree.init(); - - // Store plugin object in this element's data - $this.data('NCBOPropertyTree', ncboPropertyTree); - }); - } - -}(jQuery)); \ No newline at end of file diff --git a/app/assets/javascripts/bp_resource_index.js.erb b/app/assets/javascripts/bp_resource_index.js.erb deleted file mode 100644 index 1e285eae3..000000000 --- a/app/assets/javascripts/bp_resource_index.js.erb +++ /dev/null @@ -1,664 +0,0 @@ -// History and navigation management -(function (window, undefined) { - // Establish Variables - var History = window.History; - // History.debug.enable = true; - - // Abort if not right page - var path = currentPathArray(); - if (path[0] !== "resource_index") { - return; - } - - // Bind to State Change - History.Adapter.bind(window, 'statechange', function () { - var state = History.getState(); - if (typeof state.data.route !== "undefined") { - router.route(state.data.route, state.data); - } else { - router.route("index"); - } - }); -}(window)); - -var uri_split_chars = "\t::\t"; - -var uri_split = function(combinedURIs) { - return combinedURIs.split(uri_split_chars); -}; - -var bpResourceIndexEmbedded = false; -jQuery(document).ready(function () { - bpResourceIndexEmbedded = (jQuery("#resource_table").parents("div.resource_index_embed").length > 0); - - // Hide/Show resources - jQuery(".resource_link").live("click", function (event) { - event.preventDefault(); - switchResources(this); - }); - - // Show/Hide advanced options - jQuery("#resource_index_advanced_options").on("click", function(event) { - jQuery("#search_options").toggleClass("not_visible"); - jQuery("#hide_advanced_options").toggleClass("not_visible"); - }); - - // Spinner for pagination - jQuery(".pagination a").live("click", function () { - jQuery(this).parents("div.pagination").append('   loading ' + '<%= image_tag("spinners/spinner_000000_16px.gif", style: "vertical-align: text-bottom;") %>'); - }); - - jQuery("#resources_desc").dataTable({ - "paging": false, - "searching": false, - "columns": [ - { "type": "html" }, - { "orderable": false }, - { "type": "num-fmt", "orderSequence": [ "desc", "asc"] }, - { "orderSequence": [ "desc", "asc" ] } - ] - }); - - function formattedCls(cls) { - return "" + cls.prefLabel + " (" + cls.ontologyName + ")"; - } - - function formatCls(cls) { - var markup = cls.loading ? cls.text : formattedCls(cls); - return markup - } - - function formatClsSelection(cls) { - return formattedCls(cls); - } - - jQuery("#resource_index_classes").select2({ - allowClear: true, - placeholder: "Start typing to find classes to search the index with", - escapeMarkup: function (markup) { return markup; }, - minimumInputLength: 3, - templateResult: formatCls, - templateSelection: formatClsSelection, - - ajax: { - url: jQuery(document).data().bp.config.rest_url + "/search", - dataType: "json", - delay: 500, - data: function (params) { - var searchTerm = params.term; - - if (/[^*]$/.test(searchTerm)) { - searchTerm += '*'; - } - - return { - q: searchTerm, - apikey: jQuery(document).data().bp.config.apikey, - ontologies: currentOntologyAcronyms().join(','), - page: params.page, - }; - }, - processResults: function (data, params) { - params.page = data.page || 1; - - return { - results: $.map(data.collection, function (item) { - var ontId = item.links.ontology; - var ontName = ontId.split('/').slice(-1)[0]; - var clsId = item["@id"]; - - return { - id: ontId + uri_split_chars + clsId, - prefLabel: item.prefLabel, - ontologyName: ontName - } - }), - - pagination: { - more: data.page <= data.pageCount - } - }; - }, - cache: true - } - }); - - // TODO(jvendetti): Currently unused, but perhaps should be added to the formattedCls function - function markupClass(cls) { - // Wrap the class prefLabel in a span, indicating that the class is obsolete if necessary. - var max_word_length = 60; - var label_text = (cls.prefLabel.length > max_word_length) ? cls.prefLabel.substring(0, max_word_length) + "..." : cls.prefLabel; - var label_html = jQuery("").addClass('prefLabel').append(label_text); - if (cls.obsolete === true){ - label_html.removeClass('prefLabel'); - label_html.addClass('obsolete_class'); - label_html.attr('title', 'obsolete class'); - } - return label_html; // returns a jQuery object; use .prop('outerHTML') to get markup text. - } - - // If all classes are removed from the search, put the UI in base state - jQuery("#resource_index_classes").on('select2:unselect', function (evt) { - if(!jQuery('#resource_index_classes').val()) { - pushIndex(); - } - }); - - // Get search results - if (jQuery("#resource_index_button").length > 0) { - jQuery("#resource_index_button").click(function () { - var url = "/resource_index/resources?" + chosenSearchClassesArgs(); - pushDisplayResources(url, {classes: chosenSearchClasses()}); - getSearchResults(); - }); - } - - // Show/Hide results with zero matches - jQuery("#show_hide_no_results").live("click", function () { - jQuery("#resource_table .zero_results").toggleClass("not_visible").effect("highlight", { color: "yellow" }, 500); - jQuery("#show_hide_no_results .show_hide_text").toggleClass("not_visible"); - }); - - jQuery(".show_element_details").live("click", function (e) { - e.preventDefault(); - var el = jQuery(this); - var cleanElementId = el.data().cleanElementId; - var el_text = jQuery("#" + cleanElementId + "_text"); - el_text.toggleClass("not_visible"); - if (el_text.attr("highlighted") !== "true") { - var element = new Element(el.data().elementId, cleanElementId, chosenSearchClasses(), el.data().resourceId); - el.parent().append('highlighting... ' + '<%= image_tag("spinners/spinner_000000_16px.gif", style: "vertical-align: text-bottom;") %>' + ''); - element.highlightAnnotationPositions(); - el_text.attr("highlighted", "true"); - } - }); - - jQuery("#resource-index-help").on("click", bpPopWindow); -}); - -// Get parameters from the URL -var BP_urlParams = {}; -(function () { - var match, hashParamMatch, paramHash, - pl = /\+/g, // Regex for replacing addition symbol with a space - search = /([^&=]+)=?([^&]*)/g, - decode = function (s) { - return decodeURIComponent(s.replace(pl, " ")); - }, - query = window.location.search.substring(1); - queryH = window.location.hash.substring(1); - - while (match = search.exec(query)) { - if (hashParamMatch = /^(\w+)\[(.*)\]$/.exec(match[1])) { - paramHash = BP_urlParams[hashParamMatch[1]]; - if (paramHash === undefined) { - paramHash = {}; - } - if (paramHash[decode(hashParamMatch[2])] === undefined) { - paramHash[decode(hashParamMatch[2])] = []; - } - paramHash[decode(hashParamMatch[2])] = decode(match[2]).split(","); - BP_urlParams[hashParamMatch[1]] = paramHash; - } else { - BP_urlParams[decode(match[1])] = decode(match[2]); - } - } - - while (match = search.exec(queryH)) { - if (hashParamMatch = /^(\w+)\[(.*)\]$/.exec(match[1])) { - paramHash = BP_urlParams[hashParamMatch[1]]; - if (paramHash === undefined) { - paramHash = {}; - } - if (paramHash[decode(hashParamMatch[2])] === undefined) { - paramHash[decode(hashParamMatch[2])] = []; - } - paramHash[decode(hashParamMatch[2])] = decode(match[2]).split(","); - BP_urlParams[hashParamMatch[1]] = paramHash; - } else { - BP_urlParams[decode(match[1])] = decode(match[2]); - } - } -})(); - -function pageInit() { - var state = History.getState(); - var params = {}, paramLocations = ["root", "resources", "acronym"], route, queryString; - route = state.hash.split("?"); - queryString = (typeof route[1] !== "undefined") ? "" : route[1]; - route = route[0].split("/").slice(1); - for (var i = 0; i < route.length; i++) { - params[paramLocations[i]] = route[i]; - } - jQuery.extend(params, BP_urlParams); - BP_urlParams = params; - if (typeof params["acronym"] !== "undefined") { - router.route("resource", params); - } else if (typeof params["resources"] !== "undefined") { - router.route("resources", params); - } -} - -function pushDisplayResource(url, params) { - var route = "resource"; - if (bpResourceIndexEmbedded) { - router.route(route, params); - } else { - params["route"] = route; - History.pushState(params, document.title, url); - } -} - -function pushDisplayResources(url, params) { - var route = "resources"; - if (bpResourceIndexEmbedded) { - router.route(route, params); - } else { - params["route"] = route; - History.pushState(params, document.title, url); - } -} - -function pushIndex() { - var route = "index"; - if (bpResourceIndexEmbedded) { - router.route(route); - } else { - History.pushState(null, document.title, "/resource_index"); - } -} - -function replaceIndex() { - var route = "index"; - if (bpResourceIndexEmbedded) { - router.route(route); - } else { - History.replaceState(null, document.title, "/resource_index"); - } -} - -// This will look up any class labels that haven't already been processed. If there are none it just exits without doing anything. -// To decrease ajax calls, we also use the bp_classes_cache. This method is used via polling. -var bp_classes_cache = {}; -function lookupClassLabels() { - jQuery("#resource_results a.ri_concept[data-applied_label='false']").each(function () { - var link = jQuery(this); - var params = { conceptid: decodeURIComponent(link.data("concept_id")), ontologyid: link.data("ontology_id") }; - link.attr("data-applied_label", "true"); - - // Check to see if another thread is already making an ajax request and start polling - if (bp_classes_cache[params.ontologyid + "/" + params.conceptid] === "getting") { - return setTimeout((function () { - return applyClassLabel(link, params); - }), 100); - } - - if (typeof bp_classes_cache[params.ontologyid + "/" + params.conceptid] === "undefined") { - bp_classes_cache[params.ontologyid + "/" + params.conceptid] = "getting"; - jQuery.ajax({ - url : "/ajax/json_class", - data : params, - dataType: 'json', - success : (function (link) { - return function (data) { - bp_classes_cache[params.ontologyid + "/" + params.conceptid] = data; - if (data !== null) jQuery(link).html(data.prefLabel); - } - })(this) - }); - } - }) -} - -// Poll for class information -jQuery(document).ready(function () { - setInterval((function () { - lookupClassLabels(); - }), 1000); -}); - -// This function will poll to see if class information exists -function applyClassLabel(link, params, calledAgain) { - var class_info = bp_classes_cache[params.ontologyid + "/" + params.conceptid]; - if (class_info === "getting") { - if (typeof calledAgain !== "undefined") calledAgain = 0 - return setTimeout((function () { - return applyClassLabel(link, params, calledAgain += 1); - }), 100); - } - if (class_info !== null) jQuery(link).html(class_info.prefLabel); -} - -function Router() { - this.route = function (route, params) { - switch (route) { - case "index": - this.index(); - break; - case "resource": - this.resource(params); - break; - case "resources": - this.resources(params); - break; - } - }; - - this.index = function () { - jQuery("#results").html(""); - jQuery("#results_error").html(""); - jQuery("#initial_resources").show(); - }; - - this.resource = function (params) { - if (typeof params["classes"] === "undefined" || typeof params["acronym"] === "undefined") { - replaceIndex(); - } - displayResource(params); - }; - - this.resources = function (params) { - if (typeof params["classes"] === "undefined") { - replaceIndex(); - } - displayResources(params["classes"]); - }; - -} -router = new Router(); - -function displayResource(params) { - var resource = params["acronym"]; - if (resource === undefined || resources[resource] === undefined) { - return; - } - var name = resources[resource].name; - // Only retrieve class information if this is an initial load - if (jQuery("#resource_index_classes").val() !== null) { - showResourceResults(resource, name); - return; - } - displayClasses(params["classes"], function () { - showResourceResults(resource, name); - }); -} - -function displayResources(classes) { - // Only retrieve class information if this is an initial load - if (jQuery("#resource_index_classes").val() !== null) { - showAllResources(); - return; - } - displayClasses(classes); -} - -function displayClasses(classes, completedCallback) { - var concept, conceptOpt, ontologyId, conceptId, ontologyName, conceptsLength, params, ontClasses, chsnValue, - conceptRetreivedCount = 0, - ontClassPairs = []; - - for (ontology in classes) { - ontClasses = classes[ontology]; - for (var i = 0; i < ontClasses.length; i++) { - ontClassPairs.push([ontology, ontClasses[i]]); - } - } - conceptsLength = ontClassPairs.length; - - jQuery("#resource_index_classes").html(""); - - for (var i = 0; i < ontClassPairs.length; i++) { - ontClassPair = ontClassPairs[i]; - ontologyId = ontClassPair[0]; - ontologyAcronym = ontClassPair[0].split("/").slice(-1)[0]; - conceptId = ontClassPair[1]; - ontologyName = ont_names[ontologyId]; - params = { ontologyid: ontologyAcronym, conceptid: conceptId }; - chsnValue = ontologyId + uri_split_chars + conceptId; - jQuery.getJSON("/ajax/json_class", params, (function (ontologyAcronym, chsnValue) { - return function (data) { - jQuery("#resource_index_classes") - .append(jQuery("
          ").html(text).text(); - - // Starting offsets should be zero - for (var i = start; i <= end; i++) { - this.offsetPositions[i] = 0; - } - - for (var j = 0; j < positionsLength; j++) { - highlightType = positions[j]['type'] || "direct"; - startPosition = positions[j]['from']; - endPosition = positions[j]['to']; - - // Add the highlight opener - this.addText("", startPosition, -1); - // Add the highlight closer - this.addText("", endPosition, 0); - } - - return this.textToHighlight; - } - - this.updatePositions = function (start, added_count) { - var offset_length = this.offsetPositions.length; - for (var i = start; i <= offset_length; i++) { - this.offsetPositions[i] += added_count; - } - } - - this.addText = function (textToAdd, position, offset) { - this.textToHighlight = [this.textToHighlight.slice(0, this.getActualPosition(position) + offset), textToAdd, this.textToHighlight.slice(this.getActualPosition(position) + offset)].join(''); - this.updatePositions(position, textToAdd.length); - } - - this.getActualPosition = function (position) { - return position + this.offsetPositions[position]; - } -} - -function currentOntologyIds() { - var selectedOntIds = jQuery("#ontology_ontologyId").val(); - return selectedOntIds === null || selectedOntIds === "" ? ont_ids : selectedOntIds; -} - -function currentOntologyAcronyms() { - var ont_acronyms = new Array(); - var ontologies = currentOntologyIds(); - for(var i=0; i < ontologies.length; i++){ - ont_acronyms.push( ontologies[i].split('/').slice(-1)[0] ); - } - return ont_acronyms; -} - -function chosenSearchClasses() { - var chosenClassesMap = {}; - // get selected option values, an array of combined_uri strings. - var combined_uris = jQuery("#resource_index_classes").val(); - if (combined_uris == null){ - return chosenClassesMap; - } else if (typeof combined_uris === "string"){ - combined_uris = combined_uris.split(); // coerce it to an Array - } - for(var i=0; i < combined_uris.length; i++){ - var combined_uri = combined_uris[i]; - var split_uris = uri_split(combined_uri); - var chosen_ont_uri = split_uris[0]; - var chosen_cls_uri = split_uris[1]; - if(! chosenClassesMap.hasOwnProperty(chosen_ont_uri)) { - chosenClassesMap[chosen_ont_uri] = new Array(); - } - chosenClassesMap[chosen_ont_uri].push(chosen_cls_uri); - } - return chosenClassesMap; -} - -function chosenSearchClassesArgs(chosenClassesMap) { - if (chosenClassesMap === undefined){ - chosenClassesMap = chosenSearchClasses(); - } - var chosenClassesURI = ""; - for (var ont_uri in chosenClassesMap) { - var chosenClassArr = chosenClassesMap[ont_uri]; - chosenClassesURI += "classes[" + encodeURIComponent(ont_uri) + "]="; - chosenClassesURI += encodeURIComponent(chosenClassArr.join(',')); - chosenClassesURI += "&"; - } - return chosenClassesURI.slice(0,-1); // remove last '&' -} - diff --git a/app/assets/javascripts/bp_reviews.js b/app/assets/javascripts/bp_reviews.js deleted file mode 100644 index a1088468e..000000000 --- a/app/assets/javascripts/bp_reviews.js +++ /dev/null @@ -1,17 +0,0 @@ -function setupReviewFacebox() { - jQuery("a.create_review").attr("rel", "facebox[.facebox_review]"); - jQuery("a.create_review").facebox(); -} - -function setupReviewFaceboxSizing() { - jQuery(document).bind('afterReveal.facebox', function() { - jQuery("div.facebox_review").parents("div#facebox").width('850px'); - jQuery("div.facebox_review").width('820px'); - jQuery("div.facebox_review").parents("div#facebox").css("max-height", jQuery(window).height() - (jQuery("#facebox").offset().top - jQuery(window).scrollTop()) * 2 + "px"); - jQuery("div.facebox_review").parents("div#facebox").centerElement(); - }); -} - -jQuery(document).on("ajax:success", ".facebox_review form", function() { - location.reload(); -}); \ No newline at end of file diff --git a/app/assets/javascripts/bp_search.js.erb b/app/assets/javascripts/bp_search.js.erb deleted file mode 100644 index f8e46181a..000000000 --- a/app/assets/javascripts/bp_search.js.erb +++ /dev/null @@ -1,1055 +0,0 @@ -"use strict"; - -// History and navigation management -(function(window, undefined) { - // Establish Variables - var History = window.History; - // History.debug.enable = true; - - // Abort it not right page - var path = currentPathArray(); - if (path[0] !== "search") { - return; - } - - // Bind to State Change - History.Adapter.bind(window, 'statechange', function() { - var state = History.getState(); - autoSearch(); - }); -}(window)); - -var showAdditionalResults = function(obj, resultSelector) { - var ontAcronym = jQuery(obj).attr("data-bp_ont"); - jQuery(resultSelector + ontAcronym).toggleClass("not_visible"); - jQuery(obj).children(".hide_link").toggleClass("not_visible"); - jQuery(obj).toggleClass("not_underlined"); -}; - -var showAdditionalOntResults = function(event) { - event.preventDefault(); - showAdditionalResults(this, "#additional_ont_results_"); -}; - -var showAdditionalClsResults = function(event) { - event.preventDefault(); - showAdditionalResults(this, "#additional_cls_results_"); -}; - - -// Declare the blacklisted class ID entities at the top level, to avoid -// repetitive execution within blacklistClsIDComponents. The order of the -// declarations here matches the order of removal. The fixed strings are -// removed once, the regex strings are removed globally from the class ID. -var blacklistFixStrArr = [], - blacklistSearchWordsArr = [], // see performSearch and aggregateResultsWithSubordinateOntologies - blacklistSearchWordsArrRegex = [], - blacklistRegexArr = [], - blacklistRegexMod = "ig"; -blacklistFixStrArr.push("https://"); -blacklistFixStrArr.push("http://"); -blacklistFixStrArr.push("bioportal.bioontology.org/ontologies/"); -blacklistFixStrArr.push("purl.bioontology.org/ontology/"); -blacklistFixStrArr.push("purl.obolibrary.org/obo/"); -blacklistFixStrArr.push("swrl.stanford.edu/ontologies/"); -blacklistFixStrArr.push("mesh.owl"); // Avoids RH-MESH subordinate to MESH -blacklistRegexArr.push(new RegExp("abnormalities", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("biological", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("biology", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("bioontology", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("clinical", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("extension", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("\.gov", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("ontology", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("ontologies", blacklistRegexMod)); -blacklistRegexArr.push(new RegExp("semanticweb", blacklistRegexMod)); - -function blacklistClsIDComponents(clsID) { - var strippedID = clsID; - // remove fixed strings first - for (var i = 0; i < blacklistFixStrArr.length; i++) { - strippedID = strippedID.replace(blacklistFixStrArr[i], ""); - }; - // cleanup with regex replacements - for (var i = 0; i < blacklistRegexArr.length; i++) { - strippedID = strippedID.replace(blacklistRegexArr[i], ""); - }; - // remove search keywords (see performSearch and aggregateResultsWithSubordinateOntologies) - for (var i = 0; i < blacklistSearchWordsArrRegex.length; i++) { - strippedID = strippedID.replace(blacklistSearchWordsArrRegex[i], ""); - }; - return strippedID; -} - -function OntologyOwnsClass(clsID, ontAcronym) { - // Does the clsID contain the ontAcronym? - // Use case insensitive match - clsID = blacklistClsIDComponents(clsID); - return clsID.toUpperCase().lastIndexOf(ontAcronym) > -1; -} - -function findOntologyOwnerOfClass(clsID, ontAcronyms) { - // Find the index of cls_id in cls_list results with the cls_id in the 'owner' - // ontology (cf. ontologies that import the class, or views). - var ontAcronym = "", - ontWeight = 0, - ontIsOwner = false, - ontOwner = { - "acronym": "", - "index": null, - "weight": 0 - }; - for (var i = 0, j = ontAcronyms.length; i < j; i++) { - ontAcronym = ontAcronyms[i]; - // Does the class ID contain the ontology acronym? If so, the result is a - // potential ontology owner. Update the ontology owner, if the ontology - // acronym matches and it has a greater 'weight' than any previous ontology owner. - // Note that OntologyOwnsClass() modifies the clsID to blacklist various strings that - // cause false or misleading matches for ontology acronyms in class ID. - if (OntologyOwnsClass(clsID, ontAcronym)) { - // This weighting that places greater value on matching an ontology acronym later in the class ID. - ontWeight = ontAcronym.length * (clsID.toUpperCase().lastIndexOf(ontAcronym) + 1); - if (ontWeight > ontOwner.weight) { - ontOwner.acronym = ontAcronym; - ontOwner.index = i; - ontOwner.weight = ontWeight; - // Cannot break here, in case another acronym has greater weight. - } - } - - } - return ontOwner; -} - - - - -jQuery(document).ready(function() { - // Wire advanced search categories - jQuery("#search_categories").chosen({ - search_contains: true, - width: "432px" - }); - /* Comment it because it is changing the button value to nothing (and it is a param for chosen, don't seems useful for button) - jQuery("#search_button").button({ - search_contains: true - });*/ - jQuery("#search_button").click(function(event) { - ajax_process_halt(); - }); - jQuery("#search_keywords").click(function(event) { - ajax_process_halt(); - }); - - jQuery("#search_spinner").hide(); - - // Put cursor in search box by default - jQuery("#search_keywords").focus(); - - jQuery("#search_select_ontologies").change(function() { - if (jQuery(this).is(":checked")) { - jQuery("#ontology_picker_options").removeClass("not_visible"); - } else { - jQuery("#ontology_picker_options").addClass("not_visible"); - jQuery("#ontology_ontologyId").val(""); - jQuery("#ontology_ontologyId").trigger("chosen:updated"); - } - }); - - jQuery("#search_results a.additional_ont_results_link").live("click", showAdditionalOntResults); - jQuery("#search_results a.additional_cls_results_link").live("click", showAdditionalClsResults); - - jQuery("#search_options").hide(); - jQuery("#advanced_options").on('click', toggleAdvancedSearchOptions); - - // Events to run whenever search results are updated (mainly counts) - jQuery(document).live("search_results_updated", function() { - // Update count - jQuery("#ontologies_count_total").html(currentOntologiesCount()); - - // Tooltip for ontology counts - updatePopupCounts(); - jQuery("#ont_tooltip").tooltip({ - position: "bottom right", - opacity: "90%", - offset: [-18, 5] - }); - }); - - // Perform search - jQuery("#search_button").click(function(event) { - event.preventDefault(); - History.pushState(currentSearchParams(), document.title, "/search?" + objToQueryString(currentSearchParams())); - }); - - // Search on enter - jQuery("#search_keywords").bind("keyup", function(event) { - if (event.which == 13) { - jQuery("#search_button").click(); - } - }); - - // Details/visualize link to show details pane and visualize biomixer - jQuery.facebox.settings.closeImage = "<%= asset_path("facebox/closelabel.png") %>"; - jQuery.facebox.settings.loadingImage = "<%= asset_path("facebox/loading.gif") %>"; - - // Position of popup for details - jQuery(document).bind("reveal.facebox", function() { - if (jQuery("div.class_details_pop").is(":visible")) { - jQuery("#facebox").css("max-height", jQuery(window).height() - (jQuery("#facebox").offset().top - jQuery(window).scrollTop()) * 2 + "px"); - } - }); - - // Use pop-up with flex via an iframe for "visualize" link - jQuery("a.class_visualize").live("click", function() { - var acronym = jQuery(this).attr("data-bp_ontologyid"), - conceptid = jQuery(this).attr("data-bp_conceptid"); - jQuery("#biomixer").html('').show(); - jQuery.facebox({ - div: '#biomixer' - }); - }); - - jQuery("#search-help").on("click", bpPopWindow); - - autoSearch(); -}); - -// Automatically perform search based on input parameters -function autoSearch() { - // Check for existing parameters/queries and update UI accordingly - var params = BP_queryString(), - query = null, - ontologyIds = null, - categories = null; - - if (params.hasOwnProperty("query") || params.hasOwnProperty("q")) { - query = params.query || params.q; - jQuery("#search_keywords").val(query); - - if (params.exactmatch === "true" || params.exact_match === "true") { - if (!jQuery("#search_exact_match").is(":checked")) { - jQuery("#search_exact_match").attr("checked", true); - } - } else { - jQuery("#search_exact_match").attr("checked", false); - } - - if (params.searchproperties === "true" || params.include_properties === "true") { - if (!jQuery("#search_include_properties").is(":checked")) { - jQuery("#search_include_properties").attr("checked", true); - } - } else { - jQuery("#search_include_properties").attr("checked", false); - } - - if (params.require_definition === "true") { - if (!jQuery("#search_require_definition").is(":checked")) { - jQuery("#search_require_definition").attr("checked", true); - } - } else { - jQuery("#search_require_definition").attr("checked", false); - } - - if (params.include_views === "true") { - if (!jQuery("#search_include_views").is(":checked")) { - jQuery("#search_include_views").attr("checked", true); - } - } else { - jQuery("#search_include_views").attr("checked", false); - } - - if (params.hasOwnProperty("ontologyids") || params.hasOwnProperty("ontologies")) { - ontologyIds = params.ontologies || params.ontologyids || ""; - ontologyIds = ontologyIds.split(","); - jQuery("#ontology_ontologyId").val(ontologyIds); - jQuery("#ontology_ontologyId").trigger("chosen:updated"); - } - - if (params.hasOwnProperty("categories")) { - categories = params.categories || ""; - categories = categories.split(","); - jQuery("#search_categories").val(categories); - jQuery("#search_categories").trigger("chosen:updated"); - } - - performSearch(); - } -} - - -function currentSearchParams() { - var params = {}, ont_val = null; - // Search query - params.q = jQuery("#search_keywords").val(); - // Ontologies - ont_val = jQuery("#ontology_ontologyId").val(); - params.ontologies = (ont_val === null) ? "" : ont_val.join(","); - // Advanced options - params.include_properties = jQuery("#search_include_properties").is(":checked"); - params.include_views = jQuery("#search_include_views").is(":checked"); - params.includeObsolete = jQuery("#search_include_obsolete").is(":checked"); - // params.includeNonProduction = - // jQuery("#search_include_non_production").is(":checked"); - params.require_definition = jQuery("#search_require_definition").is(":checked"); - params.exact_match = jQuery("#search_exact_match").is(":checked"); - params.categories = jQuery("#search_categories").val() || ""; - params.lang = jQuery("#select_search_language").val() || ""; - - return params; -} - - - -function objToQueryString(obj) { - var str = [], - p = null; - for (p in obj) { - if (obj.hasOwnProperty(p)) { - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - } - } - return str.join("&"); -} - -function performSearch() { - jQuery("#search_spinner").show(); - jQuery("#search_messages").html(""); - jQuery("#search_results").html(""); - jQuery("#result_stats").html(""); - - var ont_val = jQuery("#ontology_ontologyId").val() || null, - onts = (ont_val === null) ? "" : ont_val.join(","), - query = jQuery("#search_keywords").val(), - // Advanced options - includeProps = jQuery("#search_include_properties").is(":checked"), - includeViews = jQuery("#search_include_views").is(":checked"), - includeObsolete = jQuery("#search_include_obsolete").is(":checked"), - includeNonProduction = jQuery("#search_include_non_production").is(":checked"), - includeOnlyDefinitions = jQuery("#search_require_definition").is(":checked"), - exactMatch = jQuery("#search_exact_match").is(":checked"), - categories = jQuery("#search_categories").val() || "", - language = jQuery("#select_search_language").val() || ""; - - // Set the list of search words to be blacklisted for the ontology ownership algorithm - blacklistSearchWordsArr = query.split(/\s+/); - - jQuery.ajax({ - // bp.config is created in views/layouts/_header..., which calls - // ApplicationController::bp_config_json - url: determineHTTPS(jQuery(document).data().bp.config.rest_url) + "/search", - data: { - q: query, - lang: language, - include_properties: includeProps, - include_views: includeViews, - obsolete: includeObsolete, - include_non_production: includeNonProduction, - require_definition: includeOnlyDefinitions, - exact_match: exactMatch, - categories: categories, - ontologies: onts, - pagesize: 150, - apikey: jQuery(document).data().bp.config.apikey, - userapikey: jQuery(document).data().bp.config.userapikey, - format: "jsonp", - ncbo_slice: (("ncbo_slice" in jQuery(document).data().bp.config) ? jQuery(document).data().bp.config.ncbo_slice : '') - }, - dataType: "jsonp", - success: function(data) { - var results = [], - ontologies = {}, - groupedResults = null, - result_count = jQuery("#result_stats"), - resultsByOnt = "", - resultsOntCount = "", - resultsOntDiv = ""; - if (categories.length > 0) { - data.collection = filterCategories(data.collection, categories); - } - if (!jQuery.isEmptyObject(data)) { - groupedResults = aggregateResults(data.collection); - jQuery(groupedResults).each(function() { - results.push(formatSearchResults(this)); - }); - } - // Display error message if no results found - if (data.collection.length === 0) { - result_count.html(""); - jQuery("#search_results").html("

          No matches found

          "); - } else { - if (jQuery("#ontology_ontologyId").val() === null) { - resultsOntCount = jQuery(""); - resultsOntCount.attr("id", "ontologies_count_total"); - resultsOntCount.text(groupedResults.length); - resultsByOnt = jQuery("
          "); - resultsByOnt.attr({ - "id": "ont_tooltip", - "href": "javascript:void(0)" - }); - resultsByOnt.append("Matches in "); - resultsByOnt.append(resultsOntCount); - resultsByOnt.append(" ontologies"); - resultsOntDiv = jQuery("
          "); - resultsOntDiv.attr("id", "ontology_counts"); - resultsOntDiv.addClass("ontology_counts_tooltip"); - resultsByOnt.append(resultsOntDiv); - } - result_count.html(resultsByOnt); - jQuery("#search_results").html(results.join("")); - } - jQuery("a[rel*=facebox]").facebox(); - jQuery("#search_results").show(); - jQuery("#search_spinner").hide(); - }, - error: function() { - jQuery("#search_spinner").hide(); - jQuery("#search_results").hide(); - jQuery("#search_messages").html("Problem searching, please try again"); - } - }); -} - -function aggregateResults(results) { - // class URI aggregation, promotes a class that belongs to 'owning' ontology, - // e.g. /search?q=cancer returns several hits for - // 'http://purl.obolibrary.org/obo/DOID_162' - // those results should be aggregated below the DOID ontology. - // var classes = aggregateResultsByClassURI(results); - var ontologies = aggregateResultsByOntology(results); - // return aggregateResultsByOntologyWithClasses(results, classes); - // return aggregateResultsWithoutDuplicateClasses(ontologies, classes); - // return aggregateResultsWithSubordinateOntologies(ontologies, classes); - return aggregateResultsWithSubordinateOntologies(ontologies); -} - - -function aggregateResultsWithSubordinateOntologies(ontologies) { - var i, j, - resultsWithSubordinateOntologies = [], - tmpOnt = null, - tmpResult = null, - tmpClsID = null, - tmpOntOwner = null, - ontAcronym = null, - ontAcronyms = [], - clsOntOwnerTracker = {}; - // build array of ontology acronyms - for (i = 0, j = ontologies.length; i < j; i++) { - tmpOnt = ontologies[i]; - tmpResult = tmpOnt.same_ont[0]; // primary result for this ontology - ontAcronym = ontologyIdToAcronym(tmpResult.links.ontology); - ontAcronyms.push(ontAcronym); - } - // Remove any items in blacklistSearchWordsArr that match ontology acronyms. - blacklistSearchWordsArrRegex = []; - for (var i = 0; i < blacklistSearchWordsArr.length; i++) { - // Convert blacklistSearchWordsArr to regex constructs so they are removed - // with case insensitive matches in blacklistClsIDComponents - blacklistSearchWordsArrRegex.push(new RegExp(blacklistSearchWordsArr[i], blacklistRegexMod)); - - // Check for any substring matches against ontology acronyms, where the - // acronyms are assumed to be upper case strings. (Note, cannot use the - // ontAcronyms array .indexOf() method, because it doesn't search for - // substring matches). - var searchToken = blacklistSearchWordsArr[i]; - var match = false; - for (var j = ontAcronyms.length - 1; j >= 0; j--) { - if (ontAcronyms[j].indexOf(searchToken) > -1) { - match = true; - break; - } - }; - if (match) { - // Remove this blacklisted search token because it matches or partially matches an ontology acronym. - blacklistSearchWordsArr.splice(i,1); - // Don't increment i, the slice moves everything so i+1 is now at i. - } else { - i++; // check the next search token. - } - } - // build hash of primary class results with an ontology owner - for (i = 0, j = ontologies.length; i < j; i++) { - tmpOnt = ontologies[i]; - tmpOnt.sub_ont = []; // add array for any subordinate ontology results - tmpResult = tmpOnt.same_ont[0]; - tmpClsID = tmpResult["@id"]; - if (clsOntOwnerTracker.hasOwnProperty(tmpClsID)) { - continue; - } - // find the best match for the ontology owner (must iterate over all ontAcronyms) - tmpOntOwner = findOntologyOwnerOfClass(tmpClsID, ontAcronyms); - if (tmpOntOwner.index !== null) { - // This primary class result is owned by an ontology - clsOntOwnerTracker[tmpClsID] = tmpOntOwner; - } - } - // aggregate the subordinate results below the owner ontology results - for (i = 0, j = ontologies.length; i < j; i++) { - tmpOnt = ontologies[i]; - tmpResult = tmpOnt.same_ont[0]; - tmpClsID = tmpResult["@id"]; - if (clsOntOwnerTracker.hasOwnProperty(tmpClsID)) { - // get the ontology that owns this class (if any) - tmpOntOwner = clsOntOwnerTracker[tmpClsID]; - if (tmpOntOwner.index === i) { - // the current ontology is the owner of this primary result - resultsWithSubordinateOntologies.push(tmpOnt); - } else { - // There is an owner, so put this ont result set into the sub_ont array - var tmpOwnerOnt = ontologies[tmpOntOwner.index]; - tmpOwnerOnt.sub_ont.push(tmpOnt); - } - } else { - // There is no ontology that owns this primary class result, just - // display this at the top level (it's not a subordinate) - resultsWithSubordinateOntologies.push(tmpOnt); - } - } - return resultsWithSubordinateOntologies; -} - - -function aggregateResultsByOntology(results) { - // NOTE: Cannot rely on the order of hash keys (obj properties) to preserve - // the order of the results, see - // http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop - var ontologies = { - "list": [], // used to ensure we have ordered ontologies - "hash": {} - }, - res = null, - ont = null; - for (var r in results) { - res = results[r]; - ont = res.links.ontology; - if (typeof ontologies.hash[ont] === "undefined") { - ontologies.hash[ont] = initOntologyResults(); - // Manage an ordered set of ontologies (no duplicates) - ontologies.list.push(ont); - } - ontologies.hash[ont].same_ont.push(res); - } - return resultsByOntologyArray(ontologies); -} - - -function initOntologyResults() { - return { - // classes with same URI - "same_cls": [], - // other classes from the same ontology - "same_ont": [], - // subordinate ontologies - "sub_ont": [] - } -} - - -function resultsByOntologyArray(ontologies) { - var resultsByOntology = [], - ont = null; - // iterate the ordered ontologies, not the hash keys - for (var i = 0, j = ontologies.list.length; i < j; i++) { - ont = ontologies.list[i]; - resultsByOntology.push(ontologies.hash[ont]); - } - return resultsByOntology; -} - - -function aggregateResultsByClassURI(results) { - var cls_hash = {}, res = null, - cls_id = null; - for (var r in results) { - res = results[r]; - cls_id = res['@id']; - if (typeof cls_hash[cls_id] === "undefined") { - cls_hash[cls_id] = { - "clsResults": [], - "clsOntOwner": null - }; - } - cls_hash[cls_id].clsResults.push(res); - } - promoteClassesWithOntologyOwner(cls_hash); - // passed by ref, modified in-place. - return cls_hash; -} - - -function promoteClassesWithOntologyOwner(cls_hash) { - var cls_id = null, - clsData = null, - ont_owner_result = null; - // Detect and 'promote' the class with an 'owner' ontology. - for (cls_id in cls_hash) { - clsData = cls_hash[cls_id]; - // Find the class in the 'owner' ontology (cf. ontologies that import the - // class, or views). Only promote the class result if the ontology owner - // is not already in the first position. - clsData.clsOntOwner = findClassWithOntologyOwner(cls_id, clsData.clsResults); - if (clsData.clsOntOwner.index > 0) { - // pop the owner and shift it to the top of the list; note that splice and - // unshift modify in-place so there's no need to reassign into cls_hash. - ont_owner_result = clsData.clsResults.splice(clsData.clsOntOwner.index, 1)[0]; - clsData.clsResults.unshift(ont_owner_result); - clsData.clsOntOwner.index = 0; - } - } -} - - -function findClassWithOntologyOwner(cls_id, cls_list) { - // Find the index of cls_id in cls_list results with the cls_id in the 'owner' - // ontology (cf. ontologies that import the class, or views). - var clsResult = null, - ontAcronym = "", - ontOwner = { - "index": null, - "acronym": "" - }, ontIsOwner = false; - for (var i = 0, j = cls_list.length; i < j; i++) { - clsResult = cls_list[i]; - ontAcronym = ontologyIdToAcronym(clsResult.links.ontology); - // Does the cls_id contain the ont acronym? If so, the result is a - // potential ontology owner. Update the ontology owner, if the ontology - // acronym matches and it is longer than any previous ontology owner. - ontIsOwner = OntologyOwnsClass(ontAcronym, clsID); - if (ontIsOwner && (ontAcronym.length > ontOwner.acronym.length)) { - ontOwner.acronym = ontAcronym; - ontOwner.index = i; - // console.log("Detected owner: index = " + ontOwner.index + ", ont = " + ontOwner.acronym); - } - } - return ontOwner; -} - - -var sortStringFunction = function(a, b) { - // See http://www.sitepoint.com/sophisticated-sorting-in-javascript/ - var x = String(a).toLowerCase(), - y = String(b).toLowerCase(); - return x < y ? -1 : x > y ? 1 : 0; -}; - -function sortResultsByOntology(results) { - // See http://www.sitepoint.com/sophisticated-sorting-in-javascript/ - return results.sort(function(a, b) { - var ontA = String(a.links.ontology).toLowerCase(), - ontB = String(b.links.ontology).toLowerCase(); - return ontA < ontB ? -1 : ontA > ontB ? 1 : 0; - }); -} - - -function formatSearchResults(aggOntologyResults) { - var - ontResults = aggOntologyResults.same_ont, - clsResults = aggOntologyResults.same_cls, - // init primary result values - res = ontResults.shift(), - ontAcronym = ontologyIdToAcronym(res.links.ontology), - clsID = res["@id"], - clsCode = encodeURIComponent(clsID), - label_html = classLabelSpan(res), - // init search results jQuery objects - searchResultLinks = null, - searchResultDiv = null, - additionalResultsSpan = null, - additionalResultsHide = null, - additionalOntResultsAnchor = null, - additionalOntResults = "", - additionalOntResultsAttr = null, - additionalClsResults = "", - additionalClsResultsAttr = null, - additionalClsResultsAnchor = null; - - searchResultDiv = jQuery("
          "); - searchResultDiv.addClass("search_result"); - searchResultDiv.attr("data-bp_ont_id", res.links.ontology); - searchResultDiv.append(classDiv(res, label_html, true)); - searchResultDiv.append(definitionDiv(res)); - - additionalResultsSpan = jQuery(""); - additionalResultsSpan.addClass("additional_results_link"); - additionalResultsSpan.addClass("search_result_link"); - - additionalResultsHide = jQuery(""); - additionalResultsHide.addClass("not_visible"); - additionalResultsHide.addClass("hide_link"); - additionalResultsHide.text("[hide]"); - - // Process additional ontology results - if (ontResults.length > 0) { - additionalOntResultsAttr = { - "href": "#additional_ont_results", - "data-bp_ont": ontAcronym, - "data-bp_cls": clsID - }; - additionalOntResultsAnchor = jQuery(""); - additionalOntResultsAnchor.addClass("additional_ont_results_link"); - additionalOntResultsAnchor.addClass("search_result_link"); - additionalOntResultsAnchor.attr(additionalOntResultsAttr); - additionalOntResultsAnchor.append(ontResults.length + " more from this ontology"); - additionalOntResultsAnchor.append(additionalResultsHide.clone()); - additionalResultsSpan.append(" - "); - additionalResultsSpan.append(additionalOntResultsAnchor); - additionalOntResults = formatAdditionalOntResults(ontResults, ontAcronym); - } - - // Process additional clsResults - if (clsResults.length > 0) { - additionalClsResultsAttr = { - "href": "#additional_cls_results", - "data-bp_ont": ontAcronym, - "data-bp_cls": clsID - }; - additionalClsResultsAnchor = jQuery(""); - additionalClsResultsAnchor.addClass("additional_cls_results_link"); - additionalClsResultsAnchor.addClass("search_result_link"); - additionalClsResultsAnchor.attr(additionalClsResultsAttr); - additionalClsResultsAnchor.append(clsResults.length + " more for this class"); - additionalClsResultsAnchor.append(additionalResultsHide.clone()); - additionalResultsSpan.append(" - "); - additionalResultsSpan.append(additionalClsResultsAnchor); - additionalClsResults = formatAdditionalClsResults(clsResults, ontAcronym); - } - - // Nest subordinate ontology results - var subOntResults = "", - subordinateOntTitle = ""; - if (aggOntologyResults.sub_ont.length > 0) { - subOntResults = jQuery("
          "); - subOntResults.addClass("subordinate_ont_results"); - subordinateOntTitle = jQuery("

          "); - subordinateOntTitle.addClass("subordinate_ont_results_title"); - subordinateOntTitle.addClass("search_result_link"); - subordinateOntTitle.attr("data-bp_ont", ontAcronym); - subordinateOntTitle.text("Reuses in other ontologies"); - subOntResults.append(subordinateOntTitle); - jQuery(aggOntologyResults.sub_ont).each(function() { - subOntResults.append(formatSearchResults(this)); - }); - } - - searchResultLinks = jQuery("
          "); - searchResultLinks.addClass("search_result_links"); - searchResultLinks.append(resultLinksSpan(res)); - searchResultLinks.append(additionalResultsSpan); - - searchResultDiv.append(searchResultLinks); - searchResultDiv.append(additionalOntResults); - searchResultDiv.append(additionalClsResults); - searchResultDiv.append(subOntResults); - return searchResultDiv.prop("outerHTML"); -} - - - -function formatAdditionalClsResults(clsResults, ontAcronym) { - var additionalClsTitle = null, - clsResultsFormatted = null, - searchResultDiv = null, - classLabelDiv = null, - classDetailsDiv = null; - additionalClsTitle = jQuery("

          "); - additionalClsTitle.addClass("additional_cls_results_title"); - additionalClsTitle.text("Same Class URI - Other Ontologies"); - clsResultsFormatted = jQuery("
          "); - clsResultsFormatted.attr("id", "additional_cls_results_" + ontAcronym); - clsResultsFormatted.addClass("additional_cls_results"); - clsResultsFormatted.addClass("not_visible"); - clsResultsFormatted.append(additionalClsTitle); - jQuery(clsResults).each(function() { - searchResultDiv = jQuery("
          "); - searchResultDiv.addClass("search_result_links"); - searchResultDiv.append(resultLinksSpan(this)); - // class prefLabel with ontology name - classLabelDiv = classDiv(this, classLabelSpan(this), true); - classDetailsDiv = jQuery("
          "); - classDetailsDiv.addClass("search_result_additional"); - classDetailsDiv.append(classLabelDiv); - classDetailsDiv.append(definitionDiv(this, "additional_def_container")); - classDetailsDiv.append(searchResultDiv); - clsResultsFormatted.append(classDetailsDiv); - }); - return clsResultsFormatted; -} - -function formatAdditionalOntResults(ontResults, ontAcronym) { - var additionalOntTitle = null, - ontResultsFormatted = null, - searchResultDiv = null, - classLabelDiv = null, - classDetailsDiv = null; - additionalOntTitle = jQuery(""); - additionalOntTitle.addClass("additional_ont_results_title"); - additionalOntTitle.addClass("search_result_link"); - additionalOntTitle.attr("data-bp_ont", ontAcronym); - additionalOntTitle.text("Same Ontology - Other Classes"); - ontResultsFormatted = jQuery("
          "); - ontResultsFormatted.attr("id", "additional_ont_results_" + ontAcronym); - ontResultsFormatted.addClass("not_visible"); - // ontResultsFormatted.addClass( "additional_ont_results" ); - // ontResultsFormatted.append( additionalOntTitle ); - jQuery(ontResults).each(function() { - searchResultDiv = jQuery("
          "); - searchResultDiv.addClass("search_result_links"); - searchResultDiv.append(resultLinksSpan(this)); - // class prefLabel without ontology name - classLabelDiv = classDiv(this, classLabelSpan(this), false); - classDetailsDiv = jQuery("
          "); - classDetailsDiv.addClass("search_result_additional"); - classDetailsDiv.append(classLabelDiv); - classDetailsDiv.append(definitionDiv(this, "additional_def_container")); - classDetailsDiv.append(searchResultDiv); - ontResultsFormatted.append(classDetailsDiv); - }); - return ontResultsFormatted; -} - -function updatePopupCounts() { - var ontologies = [], - result = null, - resultsCount = 0; - jQuery("#search_results div.search_result").each(function() { - result = jQuery(this); - // Add one to the additional results to get total count (1 is for the - // primary result) - resultsCount = result.children("div.additional_ont_results").find("div.search_result_additional").length + 1; - ontologies.push(result.attr("data-bp_ont_name") + " " + resultsCount + "
          "); - }); - // Sort using case insensitive sorting - ontologies.sort(sortStringFunction); - jQuery("#ontology_counts").html(ontologies.join("")); -} - - -function classLabelSpan(cls) { - // Wrap the class prefLabel in a span, indicating that the class is obsolete - // if necessary. - let prefLabel = cls.prefLabel - - if(Array.isArray(prefLabel)){ - let query = jQuery("#search_keywords").val() - // Filter labels containing the query or return the first label - let filteredLabels = prefLabel.filter(label => label.includes(query)) - // If there are matching labels, use the first one; otherwise, use the first label - prefLabel = filteredLabels.length > 0 ? filteredLabels[0] : prefLabel[0] - } - - var MAX_LENGTH = 60, - labelText = prefLabel, - labelSpan = null; - - if (labelText > MAX_LENGTH) { - labelText = prefLabel.substring(0, MAX_LENGTH) + "..."; - } - labelSpan = jQuery("").text(labelText); - if (cls.obsolete === true) { - labelSpan.addClass('obsolete_class'); - labelSpan.attr('title', 'obsolete class'); - } else { - labelSpan.addClass('prefLabel'); - } - return labelSpan; - // returns a jQuery object; use .prop('outerHTML') to get markup. -} - -function filterCategories(results, filterCats) { - var newResults = [], - result = null, - acronym = null; - jQuery(results).each(function() { - result = this; - acronym = ontologyIdToAcronym(result.links.ontology); - jQuery(filterCats).each(function() { - if (categoriesMap[this].indexOf(acronym) > -1) { - newResults.push(result); - } - }); - }); - return newResults; -} - -function shortenDefinition(def) { - var defLimit = 210, - defWords = null; - if (typeof def !== "undefined" && def !== null && def.length > 0) { - // Make sure definitions isn't an array - def = (typeof def === "string") ? def : def.join(". "); - // Strip out xml elements and/or html - def = jQuery("
          ").html(def).text(); - if (def.length > defLimit) { - defWords = def.slice(0, defLimit).split(" "); - // Remove the last word in case we got one partway through - defWords.pop(); - def = defWords.join(" ") + " ..."; - } - } - jQuery(document).trigger("search_results_updated"); - return def || ""; -} - -function advancedOptionsSelected() { - var selected = null, - check = null, - i = null, - j = null; - if (document.URL.indexOf("opt=advanced") >= 0) { - return true; - } - check = [ - - function() { - return jQuery("#search_include_properties").is(":checked"); - }, - function() { - return jQuery("#search_include_views").is(":checked"); - }, - function() { - return jQuery("#search_include_non_production").is(":checked"); - }, - function() { - return jQuery("#search_include_obsolete").is(":checked"); - }, - function() { - return jQuery("#search_only_definitions").is(":checked"); - }, - function() { - return jQuery("#search_exact_match").is(":checked"); - }, - function() { - return jQuery("#search_categories").val() !== null && (jQuery("#search_categories").val() || []).length > 0; - }, - function() { - return jQuery("#ontology_ontologyId").val() !== null && (jQuery("#ontology_ontologyId").val() || []).length > 0; - } - ]; - for (i = 0, j = check.length; i < j; i++) { - selected = check[i](); - if (selected) { - return true; - } - }; - return false; -} - -function ontologyIdToAcronym(id) { - return id.split("/").slice(-1)[0]; -} - -function getOntologyName(cls) { - var ont = jQuery(document).data().bp.ontologies[cls.links.ontology]; - if (typeof ont === 'undefined') { - return ""; - } - return " - " + ont.name + " (" + ont.acronym + ")"; -} - -function currentResultsCount() { - return jQuery(".search_result").length + jQuery(".search_result_additional").length; -} - -function currentOntologiesCount() { - return jQuery(".search_result").length; -} - -function classDiv(res, clsLabel, displayOntologyName) { - var clsID = null, - clsCode = null, - clsURI = null, - ontAcronym = null, - ontName = null, - clsAttr = null, - clsAnchor = null, - clsIdDiv = null; - ontAcronym = ontologyIdToAcronym(res.links.ontology); - clsID = res["@id"]; - clsCode = encodeURIComponent(clsID); - clsURI = "/ontologies/" + ontAcronym + "?p=classes&conceptid=" + clsCode; - ontName = displayOntologyName ? getOntologyName(res) : ""; - clsAttr = { - "title": res.prefLabel, - "data-bp_conceptid": clsID, - "data-exact_match": res.exactMatch, - "href": clsURI - }; - clsAnchor = jQuery(""); - clsAnchor.attr(clsAttr); - clsAnchor.append(clsLabel); - clsAnchor.append(ontName); - clsIdDiv = jQuery("
          "); - clsIdDiv.addClass("concept_uri"); - clsIdDiv.text(res["@id"]); - return jQuery("
          ").addClass("class_link").append(clsAnchor).append(clsIdDiv); -} - - -function resultLinksSpan(res) { - var ontAcronym = null, - clsID = null, - clsCode = null, - detailsAttr = null, - detailsAnchor = null, - vizAttr = null, - vizAnchor = null, - resLinks = null; - ontAcronym = ontologyIdToAcronym(res.links.ontology); - clsID = res["@id"]; - clsCode = encodeURIComponent(clsID); - // construct link for class 'details' in facebox - detailsAttr = { - "href": "/ajax/class_details?modal=false&ontology=" + ontAcronym + "&conceptid=" + clsCode + "&styled=false", - "rel": "facebox[.class_details_pop]" - }; - detailsAnchor = jQuery(""); - detailsAnchor.attr(detailsAttr); - detailsAnchor.addClass("class_details"); - detailsAnchor.addClass("search_result_link"); - detailsAnchor.text("details"); - // construct link for class 'visualizer' in facebox - vizAttr = { - "href": "javascript:void(0);", - "data-bp_conceptid": clsID, - "data-bp_ontologyid": ontAcronym - }; - vizAnchor = jQuery(""); - vizAnchor.attr(vizAttr); - vizAnchor.addClass("class_visualize"); - vizAnchor.addClass("search_result_link"); - vizAnchor.text("visualize"); - resLinks = jQuery(""); - resLinks.addClass("additional"); - resLinks.append(detailsAnchor); - resLinks.append(" - "); - resLinks.append(vizAnchor); - return resLinks; -} - - -function definitionDiv(res, defClass) { - defClass = typeof defClass === "undefined" ? "def_container" : defClass; - return jQuery("
          ").addClass(defClass).text(shortenDefinition(res.definition)); -} - -function determineHTTPS(url) { - return url.replace("http:", ('https:' == document.location.protocol ? 'https:' : 'http:')); -} - -function toggleAdvancedSearchOptions() { - var elem = jQuery("#advanced_options"); - var searchOptions = jQuery("#search_options"); - - if (elem.text() == elem.data("text-swap")) { - elem.text(elem.data("text-original")); - searchOptions.hide(); - } else { - elem.data("text-original", elem.text()); - elem.text(elem.data("text-swap")); - searchOptions.show(); - } -} - - diff --git a/app/assets/javascripts/components/data_table/data_table.js b/app/assets/javascripts/components/data_table/data_table.js index ecb6d1938..cca5c0a08 100644 --- a/app/assets/javascripts/components/data_table/data_table.js +++ b/app/assets/javascripts/components/data_table/data_table.js @@ -12,6 +12,8 @@ class DataTable extends HTMLElement{ super() this.tableElem = document.createElement("table") this.tableElem.style.width = "100%" + this.tableElem.classList.add('table-content') + this.tableElem.classList.add('table-content-stripped') this.container = document.createElement("div") this.container.appendChild(this.tableElem) diff --git a/app/assets/javascripts/components/instances/instances_table.js b/app/assets/javascripts/components/instances/instances_table.js index 55a036d83..8fb4371cb 100644 --- a/app/assets/javascripts/components/instances/instances_table.js +++ b/app/assets/javascripts/components/instances/instances_table.js @@ -32,7 +32,8 @@ class InstancesTable extends DataTable { id: x["table"]["@id"], label: x["table"]["label"], prefLabel: x["table"]["prefLabel"], - labelToPrint: x["table"]["labelToPrint"] + labelToPrint: x["table"]["labelToPrint"], + ontology: x["table"]["ontology"] }, x["table"]["types"], x["table"]["properties"] @@ -70,11 +71,6 @@ class InstancesTable extends DataTable { connectedCallback() { super.connectedCallback() - this.addEventListener("row-click", (e) => { - this.openPopUpDetail(e.detail.data) - }) - - } update(ontologyAcronym, classUri) { @@ -90,8 +86,8 @@ class InstancesTable extends DataTable { "name": "label", "title": 'Instance', "render": (data) => { - const {id, labelToPrint} = data - return `${labelToPrint}` + const {id, labelToPrint, ontology} = data + return `${labelToPrint}` } }] @@ -103,6 +99,7 @@ class InstancesTable extends DataTable { "render": (data) => data.map(x => { const id = x.type const label = x.labelToPrint + const acronym = x.ontology const href = (id === label ? id : `?p=classes&conceptid=${encodeURIComponent(id)}`) return `${label}` }) @@ -125,12 +122,5 @@ class InstancesTable extends DataTable { return this.classUri.length > 0 } - openPopUpDetail(data) { - let {id} = data[0] - const href = `/ontologies/${this.ontologyAcronym}/instances/${encodeURIComponent(id)}` - popUpElement({ajax: href}) - - } - } \ No newline at end of file diff --git a/app/assets/javascripts/components/pop_up.js b/app/assets/javascripts/components/pop_up.js deleted file mode 100644 index a654a6071..000000000 --- a/app/assets/javascripts/components/pop_up.js +++ /dev/null @@ -1,5 +0,0 @@ -function popUpElement(element){ - $.facebox(() => { - $.facebox(element) - }) -} \ No newline at end of file diff --git a/app/assets/javascripts/home.js.erb b/app/assets/javascripts/home.js.erb deleted file mode 100644 index 5a53b5cd3..000000000 --- a/app/assets/javascripts/home.js.erb +++ /dev/null @@ -1,142 +0,0 @@ -function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - -function jumpToValueOntology() { - var ontology = jQuery("#find_ontology")[0].value; - var ontology_id = jQuery("#find_ontology_id").val(); - - if (ontology_id == null || ontology_id == "") { - // didnt pick an ont - alert("The ontology does not exist. You must pick an ontology from the list.") - - return false; - } - - if (!!ontology_id) { - var sValue = jQuery("#find_ontology_id").val(); - if (sValue == null || sValue == "") { - sValue = data; - } - document.location="/ontologies/"+sValue; - jQuery.blockUI({ message: '

          " /> Loading Ontology...

          ' }); - return; - } -} - -function formatResultOntologySearch(value, data) { - jQuery("#find_ontology_id").val(""); - var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\ - var keywords = jQuery("#find_ontology").val().replace(specials, "\\$&").split(' ').join('|'); - var regex = new RegExp( '(' + keywords + ')', 'gi' ); - return value.replace(regex, "$1"); -} - -// Sets a hidden form value that records the virtual id when a concept is chosen in the jump to -// This is a workaround because the default autocomplete search method cannot distinguish between two -// ontologies that have the same preferred name but different ids. -function selectFindOntology(value, data){ - jQuery("#find_ontology_id").val(value.data[0]); - jQuery("#find_ontology").focus(); - jumpToValueOntology(); -} - -var ontologies_array = []; -var findOntologyInput = document.getElementById("find_ontology"); -if (findOntologyInput) { - ontologies_array = JSON.parse(findOntologyInput.dataset.ontologynames); -} - -jQuery(document).ready(function() { - jQuery("#find_ontology").autocomplete({ - selectFirst: true, - data: ontologies_array, - minChars: 1, - matchSubset: 1, - maxItemsToShow: 20, - delay: 1, - showResult: formatResultOntologySearch, - onItemSelect: selectFindOntology - }); - - var visitsChartDiv = document.getElementById("ontology-visits-chart"); - - if (visitsChartDiv) { - var ontNamesObject = JSON.parse(visitsChartDiv.dataset.ontnames); - var ontNames = Object.keys(ontNamesObject); - var ontNumbers = JSON.parse(visitsChartDiv.dataset.ontnumbers); - var onts = JSON.parse(visitsChartDiv.dataset.ontnames); - var ctx = document.getElementById("myChart"); - - var myChart = new Chart(ctx, { - type: 'horizontalBar', - data: { - labels: ontNames, - datasets: [{ - label: "Ontology Visits", - data: ontNumbers, - backgroundColor: "rgba(151,187,205,0.2)", - borderColor: "rgba(151,187,205,1)", - borderWidth: 1 - }] - }, - options: { - responsive: true, - legend: { - display: false - }, - scales: { - xAxes: [{ - ticks: { - beginAtZero: true, - stepSize: 5000, - // Return an empty string to draw the tick line but hide the tick label - // Return `null` or `undefined` to hide the tick line entirely - userCallback: function(value, index, values) { - return numberWithCommas(value); - } - } - }], - yAxes: [{ - ticks: {} - }] - }, - tooltips: { - enabled: true, - callbacks: { - title: function(tooltipItems, data) { - lbl = onts[tooltipItems[0].yLabel]; - - if (lbl.length > 45) { - lbl = lbl.substring(0, 37) + "..."; - } - return lbl + " (" + tooltipItems[0].yLabel + ")"; - }, - label: function(tooltipItem, data) { - return data.datasets[0].label + ": " + numberWithCommas(tooltipItem.xLabel); - } - } - }, - hover: { - onHover: function(e) { - jQuery("#myChart").css("cursor", e[0] ? "pointer" : "default"); - } - } - } - }); - - ctx.onclick = function(evt) { - var activePoints = myChart.getElementsAtEvent(evt); - - if (activePoints.length > 0) { - // get the internal index of slice in pie chart - var clickedElementIndex = activePoints[0]["_index"]; - // get specific label by index - var label = myChart.data.labels[clickedElementIndex]; - // get value by index - // var value = myChart.data.datasets[0].data[clickedElementIndex]; - window.location.href = "/ontologies/" + label; - } - } - } -}); \ No newline at end of file diff --git a/app/assets/javascripts/ontologies.js b/app/assets/javascripts/ontologies.js deleted file mode 100644 index 413f9f267..000000000 --- a/app/assets/javascripts/ontologies.js +++ /dev/null @@ -1,73 +0,0 @@ -/* Ontology creation & editing */ - - -function hideAllRestrictions() { - jQuery(".viewing_restriction_disabled").attr("disabled", true); - jQuery("div.viewing_restriction_types").addClass("hidden"); -} - -function showRestrictionPrivate() { - jQuery("#ontology_acl").removeAttr("disabled"); - jQuery("#viewingRestrictionsPrivate").removeClass("hidden"); -} - -function showRestrictionLicensed() { - jQuery("#ontology_licenseInformation").removeAttr("disabled"); - jQuery("#viewingRestrictionsLicensed").removeClass("hidden"); -} - -jQuery(document).ready(function () { - jQuery('#ontology-browse-help').on('click', bpPopWindow); -}); - -/* charts creation */ - -function showVisitsChat(){ - var ontologyVisitsChartCanvas = document.getElementById('visits_chart'); - - if (ontologyVisitsChartCanvas) { - var labels = JSON.parse(ontologyVisitsChartCanvas.dataset.labels); - var visits = JSON.parse(ontologyVisitsChartCanvas.dataset.visits); - var context = ontologyVisitsChartCanvas.getContext('2d'); - - var ontologyVisitsChart = new Chart(context, { - type: 'line', - data: { - labels: labels, - datasets: [{ - label: 'Visits', - data: visits, - backgroundColor: 'rgba(151, 187, 205, 0.2)', - borderColor: 'rgba(151, 187, 205, 1)', - pointBorderColor: 'rgba(151, 187, 205, 1)', - pointBackgroundColor: 'rgba(151, 187, 205, 1)', - }] - }, - options: { - responsive: true, - legend: { - display: false - }, - scales: { - yAxes: [{ - ticks: { - beginAtZero: false, - callback: function (value, index, values) { - return numberWithCommas(value); - } - } - }] - }, - tooltips: { - displayColors: false, - callbacks: { - label: function (tooltipItem, data) { - return numberWithCommas(tooltipItem.yLabel); - } - } - } - } - }); - } - return ontologyVisitsChart -} diff --git a/app/assets/javascripts/projects.js b/app/assets/javascripts/projects.js index bcfe5ccc9..757292543 100644 --- a/app/assets/javascripts/projects.js +++ b/app/assets/javascripts/projects.js @@ -23,12 +23,6 @@ jQuery(document).ready(function() { // Set the table width after it gets altered by jQuery DataTable jQuery("#projects").css("width","100%"); - if (jQuery("#projects").length) { - new jQuery.fn.dataTable.FixedHeader(projectsTable, { - header: true - }); - } - jQuery('#projects-help').on("click", bpPopWindow); }); diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js index 5eb106f47..f395ce598 100644 --- a/app/assets/javascripts/vendor.js +++ b/app/assets/javascripts/vendor.js @@ -6,34 +6,21 @@ // // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD // GO AFTER THE REQUIRES BELOW. -// +// Jquery 2 dependencies //= require jquery2 //= require jquery-migrate-1.3.0.min //= require jquery_ujs //= require jquery-ui +// popper is required by bootstrap 4 //= require popper //= require bootstrap-sprockets -// -//= require jquery.blockUI -//= require facebox -//= require thickbox -//= require fg.menu -//= require jquery.tools.min +// Other //= require jquery.dataTables -//= require dataTables.fixedHeader //= require chosen.jquery -//= require ajax-chosen -//= require jquery.cookie //= require autocomplete -//= require jquery.hoverIntent -//= require jquery.simple.tree -//= require jquery.scrollTo-1.4.0-min -//= require jquery.rating.pack -//= require history/jquery.history +// Alertify is used the admin page //= require alertify -//= require jquery.tooltip -//= require Chart.min -//= require select2 //= require jquery.readyselector +// can be removed used only in the ontology bridge form //= require trumbowyg diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index ad58c2c44..190f15528 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -8,6 +8,27 @@ margin-bottom: 30px; } +.analytics { + .card-header-1{ + color: white; font-size: 60px; font-weight: 600; word-wrap: break-word + } + + .card-header-2{ + color: white; font-size: 14px; font-weight: 400; word-wrap: break-word + } + + .card-header-3{ + color: white; opacity: 0.6; font-size: 14px; font-weight: 400; word-wrap: break-word + } +} + +.yasgui .autocompleteWrapper, .yasgui .tabContextButton{ + display: none !important; +} +.yasqe .yasqe_buttons .yasqe_share{ + display: none !important; +} + .alert-box span { font-weight: bold; text-transform: uppercase; @@ -106,10 +127,7 @@ table.dataTable tbody tr.selected { display: none; } -/* to fix facebox default sizing in chrome */ -#facebox .body { - width: 375px !important; -} + .zebra td { padding: 8px 12px 8px 12px !important; diff --git a/app/assets/stylesheets/agent_tooltip.scss b/app/assets/stylesheets/agent_tooltip.scss index 130be89f5..7460f746e 100644 --- a/app/assets/stylesheets/agent_tooltip.scss +++ b/app/assets/stylesheets/agent_tooltip.scss @@ -22,8 +22,22 @@ font-size: 16px; font-weight: 400; color: #767676; - margin-bottom: 3px; + margin-bottom: 5px; + display: flex; + align-items: center; } .agent-type-icon path{ fill: var(--primary-color) +} +.agent-dependency-icon{ + width: 20px; + height: 20px; + margin-right: 5px; +} +.agent-dependency-icon.ror{ + width: 30px; + height: 30px; +} +.agent-dependency-icon path{ + fill: #C0C0C0 } \ No newline at end of file diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/stylesheets/application.css.scss.erb index 0040a5ba4..966c35701 100755 --- a/app/assets/stylesheets/application.css.scss.erb +++ b/app/assets/stylesheets/application.css.scss.erb @@ -11,14 +11,8 @@ *= require jquery-ui *= require alertify *= require chosen - *= require facebox - *= require fg.menu *= require jquery-ui-1.8.1.custom *= require jquery.autocomplete - *= require jquery.rating - *= require jquery.tooltip - *= require thickbox - *= require select2 *= require trumbowyg *= require flag-icon *= require theme-variables @@ -39,7 +33,6 @@ @import "recommender"; @import "search"; @import "submissions"; -@import "tree"; @import "ontolobridge"; @import "fair_assement"; @import "instances_table"; @@ -49,6 +42,7 @@ @import "tom-select/dist/scss/tom-select"; @import "tippy.js/dist/tippy"; @import 'tippy.js/themes/light-border'; +@import '@triply/yasgui/build/yasgui.min'; @import "feedback"; @import "login"; @import "components/index"; diff --git a/app/assets/stylesheets/bioportal.scss b/app/assets/stylesheets/bioportal.scss index a00e324f2..9d1ae3b4d 100644 --- a/app/assets/stylesheets/bioportal.scss +++ b/app/assets/stylesheets/bioportal.scss @@ -580,7 +580,7 @@ tr.mainresource td { } .obsolete_class { - color: rgb(100,100,100); + color: rgb(100,100,100)!important; cursor: help; font-style: oblique; } @@ -650,7 +650,8 @@ div.tree_error { } #bd ul.simpleTree li{ - margin-left:-10px; + margin-left: 0px; + padding: 5px 0 0 10px; } #bd ul.simpleTree{ @@ -668,6 +669,22 @@ div.tree_error { overflow:auto; border: 1px solid #444444; */ + a.tree-link { + display: inline-block; + padding: 5px; + } + + a.tree-link.active { + cursor: default; + background-color: var(--light-color); + font-weight: bold; + border-radius: 2px; + } + + .tree-border-left{ + border-left: 2px dotted #eee; + margin-left: 2px; + } } .simpleTree li { list-style: none; diff --git a/app/assets/stylesheets/components/index.scss b/app/assets/stylesheets/components/index.scss index 8a6ff12cc..11229aadd 100644 --- a/app/assets/stylesheets/components/index.scss +++ b/app/assets/stylesheets/components/index.scss @@ -27,3 +27,4 @@ @import "alert"; @import "progress_pages"; @import "select"; +@import "search_result"; \ No newline at end of file diff --git a/app/assets/stylesheets/components/search_result.scss b/app/assets/stylesheets/components/search_result.scss new file mode 100644 index 000000000..9dfa051cb --- /dev/null +++ b/app/assets/stylesheets/components/search_result.scss @@ -0,0 +1,113 @@ +.search-result-component.sub-component{ + margin-bottom: 10px; +} + + +.search-result-component .title{ + color: var(--primary-color) !important; + font-size: 20px; + font-weight: 500; +} + +.search-result-component.sub-component .title{ + color: var(--primary-color) !important; + font-size: 18px; + font-weight: 500; +} + +.search-result-component .uri{ + color: #888888; + font-size: 14px; + margin: 3px 0; +} + +.search-result-component.sub-component .uri{ + color: #888888; + font-size: 12px; + margin: 3px 0; +} + +.search-result-component .actions{ + display: flex; + margin-top: 10px; +} + +.search-result-component.sub-component .actions{ + display: flex; + margin-top: 7px; +} + +.search-result-component .actions .button{ + display: flex; + justify-content: center; + align-items: center; + border-radius: 4px; + background-color: var(--light-color); + padding: 5px 13px; + margin-right: 10px; +} + +.search-result-component .actions .button:hover{ + cursor: pointer; +} + +.search-result-component .actions .button svg path{ + fill: var(--primary-color); +} + +.search-result-component .actions .button .text{ + color: var(--primary-color); + margin-left: 8px; +} + +.search-result-component.sub-component .actions .button .text{ + font-size: 12px; +} + +.search-result-component.sub-component .actions .button svg{ + width: 12px; +} + +.search-result-component .actions .button.icon-right .text{ + margin-right: 8px; + margin-left: 0; +} + +.more-from-ontology{ + display: flex; + margin-top: 10px; +} + +.more-from-ontology .vertical-line{ + width: 1px; + background-color: var(--primary-color); + border-radius: 100px; + margin-right: 30px; +} + +.search-result-sub-components{ + margin: 20px 0; +} + +.search-result-sub-components .reuses-title{ + display: flex; + align-items: center; + margin-bottom: 5px; +} + +.search-result-sub-components .reuses-title div{ + margin-left: 10px; + font-size: 16px; + font-weight: 600; + color: #888888; +} + +.more-from-ontology.reuses{ + background-color: #F8F8F8; +} +.more-from-ontology.reuses .vertical-line{ + background-color: #888888; +} + + + diff --git a/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss index 5c56f7d9b..6790291bd 100644 --- a/app/assets/stylesheets/home.scss +++ b/app/assets/stylesheets/home.scss @@ -153,7 +153,7 @@ i.fa.fa-caret-square-o-down { .home-statistics-container{ border-radius: 8px; box-shadow: 2px 0px 60px rgba(0, 0, 0, 0.10); - padding: 30px 40px; + padding: 30px 40px 10px 40px; } .home-statistics-container > div { diff --git a/app/assets/stylesheets/mappings.scss b/app/assets/stylesheets/mappings.scss index c4dbece77..070fafea7 100644 --- a/app/assets/stylesheets/mappings.scss +++ b/app/assets/stylesheets/mappings.scss @@ -58,7 +58,7 @@ div#map_from_concept_details_table, div#map_to_concept_details_table { overflow: auto; } -#map_to_picker, .select2-container { +#map_to_picker { width: 100% !important; } diff --git a/app/assets/stylesheets/notes.scss b/app/assets/stylesheets/notes.scss index 43003ae26..0e957b2d2 100644 --- a/app/assets/stylesheets/notes.scss +++ b/app/assets/stylesheets/notes.scss @@ -136,11 +136,6 @@ textarea.create_note_inputs { font-size: 12pt; } -.ontologies.show div#facebox div.discussion { - padding-top: 1em; - padding-left: 1.5em; -} - .reply_author { color: #234979; } diff --git a/app/assets/stylesheets/ontologies.scss b/app/assets/stylesheets/ontologies.scss index c5acad290..f02dcbd00 100644 --- a/app/assets/stylesheets/ontologies.scss +++ b/app/assets/stylesheets/ontologies.scss @@ -261,9 +261,6 @@ $widget-table-border-color: #EFEFEF; margin-right: 15px; } -#ontology-browse-help i { - margin-left: .25em; -} /************************************ /* Schemes pane diff --git a/app/assets/stylesheets/search.scss b/app/assets/stylesheets/search.scss index ab621fb36..584ee3fad 100644 --- a/app/assets/stylesheets/search.scss +++ b/app/assets/stylesheets/search.scss @@ -1,185 +1,90 @@ -form.button-to { - float: left; +.search-page-container { + display: flex; + justify-content: center; } - -#ontology_picker_head { - font-size: 10pt !important; -} - -.not_visible { - position: fixed !important; - top: -999999px !important; -} - -#search_results_container #search_results_info { - display: none !important; -} - -#search_results_filter { - display: none; -} - -#search_spinner { - display: inline-block; - padding: 8px 4px; +.search-page-subcontainer { + width: 1248px; + padding: 20px 50px; } - -#search_options #ontology_ontologyId { - display: none; -} - -.class_details_pop { - overflow: auto; - width: 750px !important; - display: block !important; +.search-page-input-container{ + width: 100%; } -table#search_results td { - vertical-align: top; - padding: 12px 8px 12px 12px; +.search-page-input{ + position: relative; + padding-bottom: 80px; } - -div.search_result { - margin-bottom: 1.5em; -} - -div.class_link a { +.search-page-input input{ + position: absolute; + border-radius: 100px; + box-shadow: rgba(100, 100, 111, 0.1) 0 7px 29px 0; + border: none; + outline: none; font-size: 18px; + padding: 15px 25px; + width: 100%; } - -span.class_def { - display: block; - margin: 3px 0 2px; - font-size: 12px; -} - -.additional_ont_results { - padding: 2em; - margin: 2em; - background-color: rgb(230,230,230); -} - -.additional_cls_results { - padding: 2em; - margin: 2em; - background-color: rgb(230,230,230); -} - -.subordinate_ont_results { - padding: 1em; - padding-left: 2em; - padding-top: 2em; - margin: 1em; - margin-left: 2em; - margin-top: 2em; - background-color: rgb(240,240,240); -} - -.subordinate_ont_results_title { - color: rgb(100,100,100); - background: rgb(200,200,200); - padding: 0.5em; +.search-page-input input:focus{ + box-shadow: rgba(100, 100, 111, 0.2) 0 7px 29px 0; } - -div.search_result_additional { - padding-left: 30px; - margin: 1em 0 1.2em; +.search-page-advanced{ + display: flex; + margin-bottom: 15px; } - -div.search_result_additional .class_link a { - font-size: 15px !important; +.search-page-advanced .left{ + width: 600px; + margin-right: 40px; } - -div.additional_results_link { - margin-top: 5px; +.search-page-advanced .filter-container{ + margin-bottom: 15px; } - -.search_result_links a { - font-size: 11px !important; - color: green; +.search-page-advanced .filter-container .title{ + margin-bottom: 5px; + color: #888888; + font-size: 14px; } -.hide_link { - text-decoration: underline; - padding-left: 7px; -} -.not_underlined { - text-decoration: none; -} -#search_results { - display: none; -} -#search_results_container { - margin-top: .5em; - clear: both; +.search-page-options{ + } - -div#search_categories_chzn { - width: 432px !important; +.search-page-button{ + position: absolute; + top: 14px; + left: 1104px; + border: none; + background: none; } - -div#search_categories_chzn .chzn-choices input { - font-style: oblique; +.search-page-button svg path{ + fill: var(--primary-color) } - -div#search_categories_chzn.chzn-container-active input { - font-style: normal !important; +.search-page-button:hover{ + cursor: pointer; } - -div#search_categories_chzn .chzn-drop { - width: 432px !important; +.search-page-options{ + display: flex; + justify-content: space-between; } - -#search_messages { - font-style: oblique; - color: gray; - padding-bottom: 7px; -} - -#result_stats a { - color: gray; +.search-page-advanced-button{ + display: flex; } - -#ontology_counts { - display: none; +.search-page-advanced-button :hover{ + cursor: pointer; } - -.popup_counts { - float: right; - display: none; +.search-page-advanced-button .text{ + margin-left: 10px; + color: var(--primary-color); } -.ontology_counts_tooltip { - background-color: #EEEEEE; - border: 1px solid black; - color: black; - font-size: 12px; - padding: 10px 15px; - text-align: left; - z-index: 999; - box-shadow: 3px 3px 7px gray; +.search-page-advanced-button .icon svg path{ + fill: var(--primary-color); } - -.definition { - cursor: help; +.search-page-number-of-results{ + color: #888888; } -.concept_uri { - font-size: 9pt; - color: gray; +.search-page-result-element{ + margin-top: 40px; } - -#search_categories_chosen .chosen-container .chosen-container-multi { - width: 432px; -} - -/* Prevents placeholder text in search input from being truncated. -/* https://github.com/harvesthq/chosen/issues/2029#issuecomment-187442769 */ -#search_options #ontology_ontologyId_chosen .search-field:only-child, -#search_options #ontology_ontologyId_chosen .search-field:only-child input { - width: 100% !important; -} - diff --git a/app/assets/stylesheets/tree.scss b/app/assets/stylesheets/tree.scss deleted file mode 100644 index ed2d3a7c7..000000000 --- a/app/assets/stylesheets/tree.scss +++ /dev/null @@ -1,323 +0,0 @@ -/******************** -## TREE VIEW -*********************/ -div.tree_error { - background: none repeat scroll 0 0 lightYellow; - font-weight: 600; - padding: 5px 10px; -} -.expansion_error { - color: red; - font-size: x-small; - font-style: oblique; - padding: 0 3px; -} -.ncboTree { - margin:0; - padding:0; - font-family: sans-serif; -} -.ncboTree li { - list-style: none; - margin:0; - padding:0 0 0 22px; - line-height: 14px; -} -.ncboTree li span { - display:inline; - clear: left; - white-space: nowrap; -} -.ncboTree ul { - margin:0; - padding:0; -} -.ncboTree .root ul { - margin:0; -} -.ncboTree .root { - margin-left:-16px !important; -} -.ncboTree .line { - padding:0; - line-height: 3px; - height:3px; - font-size:3px; - background: 0 0 no-repeat transparent url(data:image/gif;base64,R0lGODlhUAAUAIABAICAgP///yH5BAEAAAEALAAAAABQABQAAAI0jI+py+0PG4i02ouz3mryD4bi+HjkiabqYq7uC2NtTNd2MN/6Tub8D5QFh8SKr4hEHpOQAgA7); -} -.ncboTree .line-last { - padding:0; - line-height: 3px; - height:3px; - font-size:3px; - background: 0 0 no-repeat transparent url(data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==); -} -.ncboTree .line-over { - padding:0; - line-height: 3px; - height:3px; - font-size:3px; - background: 0 0 no-repeat transparent url(data:image/gif;base64,R0lGODlhUAAUAMQfAICAgEVFRUZHRoKBgUlISKKionl6enJycmRkY2tra/Ly81JSUpGRkaqqq8rLyrOzstnZ2cLDwk1NTfv8+/f39+zt7Lq7u+fn5lhYV////15eXuDg4NLS0omKipqamf///yH5BAEAAB8ALAAAAABQABQAAAV44CeOH0CSQXCubOu+cAwHAvFJ3/Jhn/Yhn8Tn8DF8Bp/Oh/HxfAqfxufxsXwiH8eH84F8Np/Lp/JRfCifyScjM7FSsrh8Tq/b73Q3fs/v+/9zeoCDhIWGMIKHiouMeImNkJGSJI+TlpeFlZibnHeanaChL5+ipZshADs=); -} -.ncboTree .line-over-last { - padding:0; - line-height: 3px; - height:3px; - font-size:3px; - background: 0 0 no-repeat transparent url(data:image/gif;base64,R0lGODlhUAAUAMQeAEVFRWtra+fn5k1NTWRkY3l6enJycklISMrLyvLy8////5qamV5eXvf397q7u1hYV8LDwkZHRoKBgbOzspGRkVJSUtnZ2eDg4Ozt7Pv8+6qqq9LS0omKiqKiov///wAAACH5BAEAAB4ALAAAAABQABQAAAVooCeOZOkBgKmubOu+8AhEhzd4lfd4jEd4AY/BU/BIPBwPxbPwdDwaz8Tj8EA8CM/GY/FcPAIPxpPwNDwZjyK2QrHf8Lh8Tq/b7/i8fs/v+/+AgYKDhIWGh4iJiouMjY6PkJGSk5SViSEAOw==); -} -.ncboTree .folder-open { - background: 0 -2px no-repeat #fff url(data:image/gif;base64,R0lGODlhEQC6BOYAAICAgP//nHueu8aaGfn7+f/4k8TExP/////bdf/vif/kf///mQQCBMS+uaVzDcyZNM/PvNrYyuzs6OTe1s3Nzevz+7fG18mWMfr7/MuYM///paBuCJpoAsXFxZ5sBpxqBNDc58+iJqRyDPfwiZlnAf/XhMzLx6NxC7q6tNe4QLqHIsiVMJKvx523zad1D9q/bLB+GL/M2/r7+n2fvLOBG36gvb2KJfDkesKPKuKzQ7WCHf/rhMmgIbeEH6JwCsaaGv3VcOLBUKx6FNHR0fH097e3t////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEYALAAAAAARALoEAAf/gEaCg4SFhoMAh4qLi4mMRkQgLTMClQIWiiwyBJydAooCnaKfh6GiFROkhqEMra0Rn4mOggIVFRK4E7CguhEQvyaqhQIQDcYNKA3ChDE1lpaYj7OP1IfT1diC19nV29zS39ze4Yrj5Ibm54Tp6trt4O+N8fLz1vX294Xs7fvq/ef/yAUMN/BbQXH59CVctxBRQ3cPD2aTiI1it4dGLFLTCK8hR0Yf6XnEGLIcyZMRUY5MyXKly4Ul8b1MGBOdSpg3aebMV1NhS5w/dQblufNeT4ZDjRatd9RhUqZL5zWFOJPoU6lR403NmPXd1q9d+YX1NxZgWYFnCaY1uBbhVa1t/yfGrTj34luvdTfm7Qi0qtK7YgGTFWyWMFrDahGzVezWL1TGciHTlWzXMVbKejHzFWoZrmaQe0F/Ftm3NGfTVjvjHW2StUzUf1UHlj2YdmHbh3En1r2Yd2PYj31HFj6ZeGXgl41nVr45NXLPzEVHJ326unPrsZ+vnt6a+2vswbXPFl+b/G3zudHvVt+b/W/wyd0Pl1+c/nH40O0v1988O/7t/EkXIHXXFeifgeH9N56C5TF4noPpQbiehO1R+B6C8Vk4n4b1cXgfhvl5uJ+I/SUIIoAkCpgigQe2aKKLGZ64oIwN0vigjRHiOKGOFfJ4IYwh+rihkB0S+SGQKBo5ov+SJcaI5IxP1hjljVPmWOWOV/aY5Y8vdumkl0FuOaSYRZJ5JJhJmrmkmk2GiSaUb0oZJ5VzWlknlndqmSeXX/bppp9p7jmmoGUSeiagcCIqp6J0Mmqno3hCqqekfP5paaCUDpppoZseemminy4aaqOjPlpqpKdOmmqlmK6qqaucwuppq7SCWquot5Kaq6m7otqrqr+yauuwuBKrq7G8IuurssAyK2yx0B4bbbLTLltts9c+K+221HJrrbfYgqttt+R+W264545r7rrosqtuu/C+K++rwdLrrL3Z4iuuvuny666/8QI8b6z1EnyvwfkivK/C/TL8r8MBQzzwrAdTnLBYxQtj3LDGD3McsccTr2moyJ2SLKvJBYMs8MoSs6xyyzC/LDPKFdN8sc0Z47yxzh3z/LHPIbeZMtAuEx2z0TMLXbPSNzOds9M7Q92z1D9THTRGWGetdTiBAAA7); -} -.ncboTree .folder-open-last { - background: 0 -2px no-repeat #fff url(data:image/gif;base64,R0lGODlhEQAWAOYAAP/bdf/vif/kf///mQQCBMS+uaVzDc/PvICAgNrYysyZNM3Nzevz++zs6OTe1smWMcXFxbfG15poAp5sBv//pZxqBPr7/MuYM6BuCLWCHX6gvdHR0cKPKuLBULB+GJlnAdq/bPfwiaRyDLe3t7eEH7q6tOKzQ7/M28mgIciVMNDc58+iJvr7+qx6FPDkeszLx7OBG5Kvx/3VcLqHIp23zb2KJcaaGqNxC6JwCv/rhH2fvNe4QP/XhPH096d1D8TExP/4k/n7+caaGXueu///nP///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEYALAAAAAARABYAAAdcgEaCg4SFhoMIh4qLi4mMj4c9KjQ6Q5ZDEYoxLEGdnkOKQ56joIeiowwOpYaiBK6uCaCJjoJDDAwNuQ6xobsJB8Avq4VDBwXHBSUFw4QnGpeXmZDT1NXW19jZ1oEAOw==); -} -.ncboTree .folder-close-last { - background: 0 -2px no-repeat #fff url(data:image/gif;base64,R0lGODlhEgAWAOYAAOvr6//bdf//nP/vif/kf//4k//UbwQCBO/v78S+ufv7+9u3UoCAgNrYysyZNMHBwevz++Tk5M/PvOTe1smWMcKPKpxqBLB+GLOBG72KJaBuCP//uuzs6LfG17e3t5lnAbWCHdu3caNxC55sBsuYM7qHIsCNKLq6tMiVMKx6FMbGxp23zceUL/r7+q58Fr/M26VzDbeEH6d1D32fvKt5E/H0936gvZKvx6h2EJpoAqampszLx8WSLdDc57a2tvn7+Xueu///////mf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEMALAAAAAASABYAAAdfgEOCg4SFhoQMh4qLjEOJjZCHNT0rM0CXQB2LNy0/np9Ai0CfB6CipD8QE6GKowevrw2hiY+DQBAQHAccE7KivQ0HEhI7rIdAEgnKCScJxoYvNpiYmpHW19jZ2tvc2YEAOw==); -} -.ncboTree .folder-close { - background: 0 -2px no-repeat #fff url(data:image/gif;base64,R0lGODlhEgC+BOYAAICAgP//mXueu/n7+f///7a2tuvr6//Ub//4k///nP/vif/bdf/kfwQCBPv7++/v78S+udu3UtrYysyZNOTe1sHBwc/PvOTk5Ovz++zs6Nu3cbB+GLqHIrfG16BuCJlnAf//usmWMcKPKrWCHcuYM5xqBL2KJbOBG55sBqNxC7e3t8iVMJ23zcCNKKt5E6h2ELq6tL/M26ampreEH/H098bGxn6gvczLx8WSLceUL658Fn2fvPr7+qVzDad1D6x6FJKvx5poAtDc5////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEMALAAAAAASAL4EAAf/gEOCg4SFhoQAh4qLjEOJjUM0Qiw7ApYCHYtAPAOdngKLAp4Nn6GjAxgUoIqiDa6uEqCJj4MCGBgZDRkUsaG8Eg0WFjerhwIWEMkQMBDFhjE2l5eZkLSQ14fW2NuC2tzY3t/V4t/h5Ivm59nq4OzX6e6D8PGO9Izz8fju+uz86v7nAJITKI5gOXvoECoyyI3hNoftFBqC+E7iRIuFKI7DKI9jR4/1QGpsNPIeyJAeSyYUeVLlwpYwWcpMGZPmTI4u193EmPPiTos9M9bEOZRnUaBHJQZFlFTh0o82oxL9qbQpwqfdrNrDinKqVKNUnWqlx7Xs2Hxn96Xtt/Zf24Bv/wfGLTj3YNirdRvmfbg34lekd7f2rRiY7OCNf6sWRrtYbWO2j91GhjtZbmW6l+0mFptZb2e+n/16HQ12M97QhE0LRo2YNGDVhlmTPDxbtknbK2Ez1u2YN2TfkoFTFm6ZOGbjml0rRu6ZOWjnoksr5ww99fTT1VtL3/76+urstcHfFp/be2zyL3GnR6+TvU/3QtW3N7+bfm/7v/EH1z+cf3H/xwGYHHfLCdicgc8hGF13BFKnoHUNYvegdgxWWGCE300YnobjcVgehud5uJ6I84FYn4n3oZifivux2J+L/8EYoIwDWuggjQfimKCOC15oo4Q8QvhjhkFS6OORNw4ZYv+RGzLZoZMfKnmilClSuaKVLWL5opYxcjmjlzUiCSSYOZK5o5k9JikmkWgKueaSbRqp5pxjvjmlnVXieaWeWfK5pZ9dAvqloGHSySahZSJ6pqJp1mkonIy6+eidk+ZZ6Z6X9pnpn5sG2umgnxbq6KiHhpqoqYui2mippEKqqqStUhqrpbNiWqumt3Kaq6e7gtqrqKwG6+qvpxKbqrGrDiusrMvS2qytz+Iara7T8lqtr9cCq+y2zHLrrLfQgiutuNSSa6252KKrbbfsfttuuO+OG2+5855bb7r3ruvuvvDyK6+/9AJsr8D4Eqxvvwj/m3DACw/ccMEPH6zwxAxT7LBixRBjLHHFHF/cccYfb+zxyCCTLHLJKJ+scrHZsqyuy/nCbLDMEdOssc0h42yyzinzvPKxLQP9stAxEz2z0TUjfbPSOTO9s9M9Q/1zskVTfbTVSWO9tNZNn+T112CHLTY3gQAAOw==); -} -.ncboTree .doc { - background: 0 -1px no-repeat #fff url(data:image/gif;base64,R0lGODlhEgC4BOYAAICAgNvt/3V4o+Hw/+n1/+vr67e3t7a2tv///8nk/4ep3NHp/3SAsvj7/+32//v7++/v78HBwf39/djr//P5/8Df/8Ph//7//+Tk5Gdtnvz9/213qIGVw2lwotvn98bGxoyu33J8rpOq0n2Jto+z5fT09X+OvMDA//n5+W96rOTx/4SbyvX6/2t0pf7+/nN+sIqo2fb6/9jl9s7T/2Vqml5hkPf399nh7ZK877rd/83m/3qDsJS/8lWn7ZCjy87n/5G46oeh0v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEIALAAAAAASALgEAAf/gEKCg4SFhoQAh4qLjEKJjZCNj5GUhpOVmI6aiZyZjJeekaChkqSVo6aKqKmWrJCrroOwsZq0qra3uK26hbOxvq7ArMKpxKbGpMihyp7Mmc6Y0Ke8vdSI1rLYgtKU3KLatdjer+Djpdrmn+Xr6Ozi7tbpi/K57+339vnx8NT0h/679PXjxwtgNXz7EA5UWJCgLoPXGD50iAtiNokVKdqyuE0jLY7hEgpsiHGjx18ng6UctrJYy2Mvk8VcNrNZzWc3o+WcVvLjzm4/v/VEOVRlUZZHXSaFuVRmU5pPbUbFOVVnVZ4jJ14FulVo1oxdyYU999XkWHVn5wUVW9Zn2noi/+MubEuUrlG7SPEq1cuUr1O/UAFLFUyVsFXDWOWSRMyVsVfFWh2zhQxWMlnKZi2j1az27b+1l+didssZrujTi0fXVX2XdV7Xe2H3lf2XdmDbg3EX1n2Yd2LUkX03Fv4YeGXik41nRh46tXLSzDdH71z6s+eA00073x78+WrvrcG/Fh+b/GzztdHfVp+b/W73veH/5n5c/nD7xekvx59cP3T+zXXn33cDhlfgeAeWl+B5C6bX4HoPthfhexPGV+F8AmZY34X3cZifhvt52B+I/4kY4IYkEpiigSsi2KKCLzIYo4MzQlijhDdSmKOFO2KI4o8h9tihkB8CWSKRIxqpov+SLDLpopMwQimjlDRSaaOVOGKpo5Y8culjkF4OGWaRYJZ55JhJmrmkmk2y+aSbUcI5pZxV0nmlnVniuaWeXfL55Zl+ihkomYAWuqahbSL6pqJxMjqno3VCeqekeVK6p6V9YvrnoZwm2uminzYa6qOjRlrqpKdWmuqlq2ba6qaexgqqrKLSSqqtpuKKqq6q8sqqr64CC+usxNZa7K3H5prsrsv22uyvzwYb7bDGVoustcpiy6y2znILrbfSgkvtteRmW+6253ab7rfrhtvuuObGi6686tLLrr3u4gvvvPzW2++9/+Yb8L7+FgywwQIjTPDBDCfc8MIORwzxxIJqWvETqxcLm/G0G4vb8bsfgyPyyNQEAgA7); -} -.ncboTree .doc-last { - background: 0 -1px no-repeat #fff url(data:image/gif;base64,R0lGODlhEgAWAOYAANvt/3V4o+Hw/+n1/4CAgOvr67e3t7a2ttHp/8nk/4ep3HSAsu32//v7++/v7/j7/8HBweTk5Njr/8Df/8Ph//39/fP5//7//5S/8pCjy4GVw1Wn7brd/2lwooqo2dvn98bGxn2Jtv7+/n+OvISbynJ8rvT09fn5+c7T//f39+Tx/8DA/5G46mdtnm13qM7n/296rHN+sJK872Vqmtjl9s3m/2t0pfX6/3qDsIeh0oyu3/z9/15hkI+z5fb6/9nh7ZOq0v///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEIALAAAAAASABYAAAcpgEKCg4SFhoQEh4qLjEKJjZCNj5GUhpOVmI6aiZyZnp+goaKjpKWmg4EAOw==); -} -.ncboTree .doc a.active { - padding-left: 4px; - margin-left: -4px; -} -.ncboTree .ajax { - background: no-repeat 0 0 #ffffff url(data:image/gif;base64,R0lGODlhEAAQAMQAAP///+7u7t3d3bu7u6qqqpmZmYiIiHd3d2ZmZlVVVURERDMzMyIiIhEREQARAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBwAQACwAAAAAEAAQAAAFdyAkQgGJJOWoQgIjBM8jkKsoPEzgyMGsCjPDw7ADpkQBxRDmSCRetpRA6Rj4kFBkgLC4IlUGhbNQIwXOYYWCXDufzYPDMaoKGBoKb886OjAKdgZAAgQkfCwzAgsDBAUCgl8jAQkHEAVkAoA1AgczlyIDczUDA2UhACH5BAUHABAALAAAAAAPABAAAAVjICSO0IGIATkqIiMKDaGKC8Q49jPMYsE0hQdrlABCGgvT45FKiRKQhWA0mPKGPAgBcTjsspBCAoH4gl+FmXNEUEBVAYHToJAVZK/XWoQQDAgBZioHaX8igigFKYYQVlkCjiMhACH5BAUHABAALAAAAAAQAA8AAAVgICSOUGGQqIiIChMESyo6CdQGdRqUENESI8FAdFgAFwqDISYwPB4CVSMnEhSej+FogNhtHyfRQFmIol5owmEta/fcKITB6y4choMBmk7yGgSAEAJ8JAVDgQFmKUCCZnwhACH5BAUHABAALAAAAAAQABAAAAViICSOYkGe4hFAiSImAwotB+si6Co2QxvjAYHIgBAqDoWCK2Bq6A40iA4yYMggNZKwGFgVCAQZotFwwJIF4QnxaC9IsZNgLtAJDKbraJCGzPVSIgEDXVNXA0JdgH6ChoCKKCEAIfkEBQcAEAAsAAAAABAADgAABUkgJI7QcZComIjPw6bs2kINLB5uW9Bo0gyQx8LkKgVHiccKVdyRlqjFSAApOKOtR810StVeU9RAmLqOxi0qRG3LptikAVQEh4UAACH5BAUHABAALAAAAAAQABAAAAVxICSO0DCQKBQQonGIh5AGB2sYkMHIqYAIN0EDRxoQZIaC6bAoMRSiwMAwCIwCggRkwRMJWKSAomBVCc5lUiGRUBjO6FSBwWggwijBooDCdiFfIlBRAlYBZQ0PWRANaSkED1oQYHgjDA8nM3kPfCmejiEAIfkEBQcAEAAsAAAAABAAEAAABWAgJI6QIJCoOIhFwabsSbiFAotGMEMKgZoB3cBUQIgURpFgmEI0EqjACYXwiYJBGAGBgGIDWsVicbiNEgSsGbKCIMCwA4IBCRgXt8bDACkvYQF6U1OADg8mDlaACQtwJCEAIfkEBQcAEAAsAAABABAADwAABV4gJEKCOAwiMa4Q2qIDwq4wiriBmItCCREHUsIwCgh2q8MiyEKODK7ZbHCoqqSjWGKI1d2kRp+RAWGyHg+DQUEmKliGx4HBKECIMwG61AgssAQPKA19EAxRKz4QCVIhACH5BAUHABAALAAAAAAQABAAAAVjICSOUBCQqHhCgiAOKyqcLVvEZOC2geGiK5NpQBAZCilgAYFMogo/J0lgqEpHgoO2+GIMUL6p4vFojhQNg8rxWLgYBQJCASkwEKLC17hYFJtRIwwBfRAJDk4ObwsidEkrWkkhACH5BAUHABAALAAAAQAQAA8AAAVcICSOUGAGAqmKpjis6vmuqSrUxQyPhDEEtpUOgmgYETCCcrB4OBWwQsGHEhQatVFhB/mNAojFVsQgBhgKpSHRTRxEhGwhoRg0CCXYAkKHHPZCZRAKUERZMAYGMCEAIfkEBQcAEAAsAAABABAADwAABV0gJI4kFJToGAilwKLCST6PUcrB8A70844CXenwILRkIoYyBRk4BQlHo3FIOQmvAEGBMpYSop/IgPBCFpCqIuEsIESHgkgoJxwQAjSzwb1DClwwgQhgAVVMIgVyKCEAIfkECQcAEAAsAAAAABAAEAAABWQgJI5kSQ6NYK7Dw6xr8hCw+ELC85hCIAq3Am0U6JUKjkHJNzIsFAqDqShQHRhY6bKqgvgGCZOSFDhAUiWCYQwJSxGHKqGAE/5EqIHBjOgyRQELCBB7EAQHfySDhGYQdDWGQyUhADs=); - height: 16px; - display:none; -} -.ncboTree .ajax li { - display:none; - margin:0; - padding:0; -} -.ncboTree .trigger { - display:inline; - margin-left:-28px; - width: 28px; - height: 11px; - cursor:pointer; -} -.ncboTree .text { - cursor: default; -} -.ncboTree .active { - cursor: default; - background-color: #B9D5E4; - font-weight: bold; - padding-top: 1px; - padding-right: 4px; - padding-bottom: 1px; - padding-left: 0px; - line-height: 16px; -} -.ncboTree a, .ncboTree a:hover { - text-decoration: none; - color: black; - font-size: 11pt; -} -.ncboTree a:hover { - cursor: pointer; -} - -/* - * jQuery UI CSS Framework 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Theming/API - */ - -/* Layout helpers -----------------------------------*/ -.ncboAutocomplete .ui-front { z-index: 100; } -.ncboAutocomplete .ui-helper-hidden { display: none; } -.ncboAutocomplete .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } -.ncboAutocomplete .ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } -.ncboAutocomplete .ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } -.ncboAutocomplete .ui-helper-clearfix { display: inline-block; } -/* required comment for clearfix to work in Opera \*/ -* html .ncboAutocomplete .ui-helper-clearfix { height:1%; } -.ncboAutocomplete .ui-helper-clearfix { display:block; } -/* end clearfix */ -.ncboAutocomplete .ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } - - -/* Interaction Cues -----------------------------------*/ -.ncboAutocomplete .ui-state-disabled { cursor: default !important; } - - -/* Icons -----------------------------------*/ - -/* states and images */ -.ncboAutocomplete .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } - - -/* Misc visuals -----------------------------------*/ - -/* Overlays */ -.ncboAutocomplete .ui-widget-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; } - -.jsonSuggest a { - font-size: .8em; -} - -.jsonSuggest a:hover { - cursor: pointer; - font-size: .8em; -} - -/* - * jQuery UI CSS Framework 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Theming/API - * - * To view and modify this theme, visit http://jqueryui.com/themeroller/?ctl=themeroller - */ - - -/* Component containers -----------------------------------*/ -.ncboAutocomplete .ui-widget { font-family: Arial,sans-serif; font-size: 1em; } -.ncboAutocomplete .ui-widget .ui-widget { font-size: 1em; } -.ncboAutocomplete .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Arial,sans-serif; font-size: 1em; } -.ncboAutocomplete .ui-widget-content { border: 1px solid #B6B6B6; background: #ffffff; color: #4F4F4F; } -.ncboAutocomplete .ui-widget-content a { color: #4F4F4F; } -.ncboAutocomplete .ui-widget-header { border: 1px solid #B6B6B6; color: #4F4F4F; font-weight: bold; } -.ncboAutocomplete .ui-widget-header { - background: #ededed 0 0 repeat-x; /* Old browsers */ - background: -moz-linear-gradient(to bottom, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(to bottom, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(to bottom, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(to bottom, #ededed 0%,#c4c4c4 100%); /* IE10+ */ - background: linear-gradient(to bottom, #ededed 0%,#c4c4c4 100%); /* W3C */ -} -.ncboAutocomplete .ui-widget-header a { color: #4F4F4F; } - -/* Interaction states -----------------------------------*/ -.ncboAutocomplete .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #B6B6B6; font-weight: normal; color: #4F4F4F; } -.ncboAutocomplete .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { - background: #ededed 0 0 repeat-x; /* Old browsers */ - background: -moz-linear-gradient(to bottom, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(to bottom, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(to bottom, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(to bottom, #ededed 0%,#c4c4c4 100%); /* IE10+ */ - background: linear-gradient(to bottom, #ededed 0%,#c4c4c4 100%); /* W3C */ - -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; - -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; - box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; -} -.ncboAutocomplete .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #4F4F4F; text-decoration: none; } -.ncboAutocomplete .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #9D9D9D; font-weight: normal; color: #313131; } -.ncboAutocomplete .ui-state-hover a, .ui-state-hover a:hover { color: #313131; text-decoration: none; } -.ncboAutocomplete .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { - outline: none; - color: #1c4257; border: 1px solid #7096ab; - background: #ededed 0 -50px repeat-x; /* Old browsers */ - background: -moz-linear-gradient(to bottom, #b9e0f5 0%, #92bdd6 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b9e0f5), color-stop(100%,#92bdd6)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(to bottom, #b9e0f5 0%,#92bdd6 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(to bottom, #b9e0f5 0%,#92bdd6 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(to bottom, #b9e0f5 0%,#92bdd6 100%); /* IE10+ */ - background: linear-gradient(to bottom, #b9e0f5 0%,#92bdd6 100%); /* W3C */ - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} -.ncboAutocomplete .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #313131; text-decoration: none; } -.ncboAutocomplete .ui-widget :active { outline: none; } - -/* Icons -----------------------------------*/ - -/* Misc visuals -----------------------------------*/ - -/* - * jQuery UI Autocomplete 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Autocomplete#theming - */ -.ncboAutocomplete .ui-autocomplete { - position: absolute; cursor: default; z-index: 3; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.3); - -webkit-box-shadow: 0 1px 5px rgba(0,0,0,0.3); - box-shadow: 0 1px 5px rgba(0,0,0,0.3); -} - -/* workarounds */ -* html .ncboAutocomplete .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ - -/* - * jQuery UI Menu 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Menu#theming - */ -.ncboAutocomplete .ui-menu { - list-style:none; - padding: 2px; - margin: 0; - display:block; - float: left; -} -.ncboAutocomplete .ui-menu .ui-menu { - margin-top: -3px; -} -.ncboAutocomplete .ui-menu .ui-menu-item { - margin:0; - padding: 0; - zoom: 1; - float: left; - clear: left; - width: 100%; -} -.ncboAutocomplete .ui-menu .ui-menu-item a { - text-decoration:none; - display:block; - padding:.2em .4em; - line-height:1.5; - zoom:1; -} -.ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-focus, -.ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-hover, -.ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-active { - font-weight: normal; - margin: -1px; - background: #5f83b9; - color: #FFFFFF; - text-shadow: 0px 1px 1px #234386; - border-color: #466086; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; -} diff --git a/app/components/chips_component.rb b/app/components/chips_component.rb index 0e9628bec..9a2a4f009 100644 --- a/app/components/chips_component.rb +++ b/app/components/chips_component.rb @@ -1,10 +1,10 @@ class ChipsComponent < ViewComponent::Base renders_one :count - def initialize(id:nil, name:, label: nil, value:, checked: false) + def initialize(id:nil, name:, label: nil, value: nil, checked: false) @id = id || name @name = name - @value = value + @value = value || 'true' @checked = checked @label = label || @value end diff --git a/app/components/display/alert_component.rb b/app/components/display/alert_component.rb index 6035f36f2..a04ea94d4 100644 --- a/app/components/display/alert_component.rb +++ b/app/components/display/alert_component.rb @@ -1,10 +1,11 @@ class Display::AlertComponent < ViewComponent::Base - def initialize(message: nil, closable: true, type: "info", auto_close_delay: nil, button: nil) + def initialize(id: nil, message: nil, closable: true, type: "info", auto_close_delay: nil, button: nil) @message = message @closable = closable @type = type @auto_close_delay = auto_close_delay @button = button + @id = id end def closable? diff --git a/app/components/display/alert_component/alert_component.html.haml b/app/components/display/alert_component/alert_component.html.haml index 63a4aa5b3..7dcce0164 100644 --- a/app/components/display/alert_component/alert_component.html.haml +++ b/app/components/display/alert_component/alert_component.html.haml @@ -1,4 +1,4 @@ -.alert-container{data: {controller: "alert-component", +.alert-container{id: @id, data: {controller: "alert-component", 'alert-component-auto-close-value': auto_close?, 'alert-component-auto-close-after-value': @auto_close_delay, }, diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb new file mode 100644 index 000000000..026c712ad --- /dev/null +++ b/app/components/display/search_result_component.rb @@ -0,0 +1,53 @@ +class Display::SearchResultComponent < ViewComponent::Base + include ModalHelper + renders_many :subresults, Display::SearchResultComponent + renders_many :reuses, Display::SearchResultComponent + def initialize(number: 0,title: nil, ontology_acronym: nil ,uri: nil, definition: nil, link: nil, is_sub_component: false) + @title = title + @uri = uri + @definition = definition + @link = link + @is_sub_component = is_sub_component + @ontology_acronym = ontology_acronym + @number = number.to_s + end + + def sub_component_class + @is_sub_component ? 'sub-component' : '' + end + + def sub_ontologies_id + string = @number+'_sub_ontologies' + end + + def reuses_id + string = @number+'_reuses' + end + + def details_button + link_to_modal(nil, "/ajax/class_details?modal=true&ontology=#{@ontology_acronym}&conceptid=#{@uri}&styled=false", data: { show_modal_title_value: @title, show_modal_size_value: 'modal-xl' }) do + content_tag(:div, class: 'button') do + concat inline_svg_tag('icons/details.svg') + concat content_tag(:div, class: 'text') { t('search.result_component.details') } + end + end + end + + def visualize_button + link_to_modal(nil, "/ajax/biomixer/?ontology=#{@ontology_acronym}&conceptid=#{@uri}", data: { show_modal_title_value: @title, show_modal_size_value: 'modal-xl' }) do + content_tag(:div, class: 'button') do + concat inline_svg_tag('icons/visualize.svg') + concat content_tag(:div, class: 'text') { t('search.result_component.visualize') } + end + end + end + + def reveal_ontologies_button(text,id) + content_tag(:div, class: 'button icon-right', 'data-action': "click->reveal-component#toggle", 'data-id': id) do + concat(content_tag(:div, class: 'text') do + text + end) + concat(inline_svg_tag("icons/arrow-down.svg")) + end + end +end \ No newline at end of file diff --git a/app/components/display/search_result_component/search_result_component.html.haml b/app/components/display/search_result_component/search_result_component.html.haml new file mode 100644 index 000000000..613b089d2 --- /dev/null +++ b/app/components/display/search_result_component/search_result_component.html.haml @@ -0,0 +1,35 @@ +.search-result-component{class: sub_component_class, 'data-controller': 'reveal-component'} + %a.title{href: @link} + = @title + - if @uri + .uri + = @uri + - if @definition + .text + = @definition + .actions + = details_button + = visualize_button + - if subresults? + = reveal_ontologies_button("#{subresults.size} #{t('search.result_component.more_from_ontology')}", sub_ontologies_id) + - if reuses? + = reveal_ontologies_button("#{t('search.result_component.reuses_in')} #{reuses.size} ontologies", reuses_id) + - if subresults? + .more-from-ontology.d-none{id: sub_ontologies_id} + .vertical-line + .search-result-sub-components + - subresults.each do |result| + .search-result-sub-component + = result + - if reuses? + .more-from-ontology.reuses.d-none{id: reuses_id} + .vertical-line + .search-result-sub-components + .reuses-title + = inline_svg_tag 'icons/reuses.svg' + %div + = t('search.result_component.reuses_in_other_ontologies') + - reuses.each do |reuse| + .search-result-sub-component + = reuse + \ No newline at end of file diff --git a/app/components/layout/reveal_component/reveal_component_controller.js b/app/components/layout/reveal_component/reveal_component_controller.js index 52badb599..e30df8d76 100644 --- a/app/components/layout/reveal_component/reveal_component_controller.js +++ b/app/components/layout/reveal_component/reveal_component_controller.js @@ -1,24 +1,35 @@ -import Reveal from 'stimulus-reveal-controller' +import { Controller } from "@hotwired/stimulus" -export default class extends Reveal { +export default class extends Controller{ static values = { - condition: String + condition: String, + hiddenClass : {type: String, default: "d-none"} } - connect() { - super.connect() - } + static targets = ["hideButton", "showButton", 'item' ] toggle(event) { if (!this.conditionValue) { - super.toggle() + this.#toggle(event) } else if (this.#shown() && !this.#conditionChecked(event)) { - super.toggle() + this.#toggle(event) } else if (!this.#shown() && this.#conditionChecked(event)) { - super.toggle() + this.#toggle(event) } } + show(event){ + this.#getItems(event).classList.remove(this.hiddenClassValue) + this.hideButtonTarget.classList.remove(this.hiddenClassValue) + this.showButtonTarget.classList.add(this.hiddenClassValue) + } + hide(event){ + this.#getItems(event).classList.add(this.hiddenClassValue) + this.hideButtonTarget.classList.add(this.hiddenClassValue) + this.showButtonTarget.classList.remove(this.hiddenClassValue) + } + + #conditionChecked(event) { return this.conditionValue === event.target.value } @@ -27,4 +38,24 @@ export default class extends Reveal { return !this.itemTargets[0].classList.contains(this.class); } + #toggle(event) { + this.#getItems(event).forEach((s) => { + s.classList.toggle(this.hiddenClassValue); + }); + } + + #ItemById(event){ + let button = event.target.closest("[data-id]"); + return document.getElementById(button.dataset.id); + } + #getItems(event){ + let items + if(this.hasItemTarget){ + items = this.itemTarget + } else { + items = [this.#ItemById(event)] + } + return items + } + } \ No newline at end of file diff --git a/app/components/table_component.rb b/app/components/table_component.rb index 9670464db..e3ce96e58 100644 --- a/app/components/table_component.rb +++ b/app/components/table_component.rb @@ -5,12 +5,13 @@ class TableComponent < ViewComponent::Base renders_one :header, TableRowComponent renders_many :rows, TableRowComponent - def initialize(id: '', stripped: true, borderless: false, layout_fixed: false ) + def initialize(id: '', stripped: true, borderless: false, layout_fixed: false, custom_class: '' ) super @id = id @stripped = stripped @borderless = borderless @layout_fixed = layout_fixed + @custom_class = custom_class end def stripped_class diff --git a/app/components/table_component/table_component.html.haml b/app/components/table_component/table_component.html.haml index ed479fb66..2c0ca196d 100644 --- a/app/components/table_component/table_component.html.haml +++ b/app/components/table_component/table_component.html.haml @@ -1,4 +1,4 @@ -%table.table-content{id: @id, class: stripped_class + ' ' + borderless_class + ' ' + layout_fixed_class} +%table.table-content{id: @id, class: stripped_class + ' ' + borderless_class + ' ' + layout_fixed_class + @custom_class} %thead = header %tbody{id: "#{@id}_table_body"} diff --git a/app/components/tabs_container_component/tabs_container_component.html.haml b/app/components/tabs_container_component/tabs_container_component.html.haml index b47c875f3..a8c955a3c 100644 --- a/app/components/tabs_container_component/tabs_container_component.html.haml +++ b/app/components/tabs_container_component/tabs_container_component.html.haml @@ -1,5 +1,5 @@ %div{data: {controller:'tabs-container'}, class: container_class, id: @id} - %div + %div{class: !pinned_right? && 'justify-content-center'} .tab-items.nav - items.each do |item| %div{data: tabs_container_data(item), class: item.active_class + ' nav-item'} diff --git a/app/components/tree_infinite_scroll_component/tree_infinite_scroll_component.html.haml b/app/components/tree_infinite_scroll_component/tree_infinite_scroll_component.html.haml index cbaaba03a..386fa14dd 100644 --- a/app/components/tree_infinite_scroll_component/tree_infinite_scroll_component.html.haml +++ b/app/components/tree_infinite_scroll_component/tree_infinite_scroll_component.html.haml @@ -4,10 +4,8 @@ current_page: @current_page, next_page: @next_page) do |c| %div - %ul.simpleTree{data:{controller: 'simple-tree','simple-tree': { 'auto-click-value': auto_click? }, action: 'clicked->history#updateURL'}} - %li.root - %ul - = content + = render TreeViewComponent.new(id: nil, auto_click: auto_click?) do + = content - c.error do %div.text-wrap diff --git a/app/components/tree_link_component.rb b/app/components/tree_link_component.rb new file mode 100644 index 000000000..81f510fb5 --- /dev/null +++ b/app/components/tree_link_component.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +class TreeLinkComponent < ViewComponent::Base + include MultiLanguagesHelper + def initialize(child:, href:, children_href: , selected: false , data: {}, muted: false, target_frame: nil) + @child = child + @active_style = selected ? 'active' : '' + #@icons = child.relation_icon(node) + @muted_style = muted ? 'text-muted' : '' + @href = href + @children_link = children_href + label = @child.prefLabel rescue @child.id + if label.nil? + @pref_label_html = child.id.split('/').last + else + pref_label_lang, @pref_label_html = select_language_label(label) + pref_label_lang = pref_label_lang.to_s.upcase + @tooltip = pref_label_lang.eql?("@NONE") ? "" : pref_label_lang + + if child.obsolete? + @pref_label_html = "#{@pref_label_html}".html_safe + end + end + @data ||= { controller: 'tooltip', 'tooltip-position-value': 'right', turbo: true, 'turbo-frame': target_frame, action: 'click->simple-tree#select'} + + @data.merge!(data) do |_, old, new| + "#{old} #{new}" + end + end + + + # This gives a very hacky short code to use to uniquely represent a class + # based on its parent in a tree. Used for unique ids in HTML for the tree view + def short_uuid + rand(36 ** 8).to_s(36) + end + + # TDOD check where used + def child_id + @child.id.to_s.split('/').last + end + + def open? + @child.expanded? ? 'open' : '' + end + + def border_left + !@child.hasChildren ? 'pl-3 tree-border-left' : '' + end + + def li_id + @child.id.eql?('bp_fake_root') ? 'bp_fake_root' : short_uuid + end + + def self.tree_close_icon + "".html_safe + end + + def open_children_link + return unless @child.hasChildren + if @child.expanded? + self.class.tree_close_icon + else + content_tag('turbo_frame', id: "#{child_id}_open_link") do + link_to @children_link, + data: { turbo: true, turbo_frame: "#{child_id + '_childs'}" } do + content_tag(:i, nil, class: "fas fa-chevron-right") + end + end + end + + end + +end diff --git a/app/components/tree_link_component/tree_link_component.html.haml b/app/components/tree_link_component/tree_link_component.html.haml new file mode 100644 index 000000000..2bf3815b0 --- /dev/null +++ b/app/components/tree_link_component/tree_link_component.html.haml @@ -0,0 +1,10 @@ +%li{id:li_id , class: open?} + = open_children_link + %a{id: @child.id, data: @data, title: @tooltip, + href: @href, class: "tree-link #{@muted_style} #{@active_style} #{border_left} #{open?}"} + = @pref_label_html + + - if @child.hasChildren && !@child.expanded? + = render TurboFrameComponent.new(id: "#{child_id}_childs") + - elsif @child.expanded? + = content \ No newline at end of file diff --git a/app/components/tree_view_component.rb b/app/components/tree_view_component.rb new file mode 100644 index 000000000..4570bf8d1 --- /dev/null +++ b/app/components/tree_view_component.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class TreeViewComponent < ViewComponent::Base + include Turbo::FramesHelper + + + renders_many :children, TreeLinkComponent + + def initialize(id:, auto_click: false, sub_tree: false, **html_options) + @id = id + @auto_click = auto_click + @html_options = html_options + @sub_tree = sub_tree + end + + private + + def sub_tree? + @sub_tree + end + + def tree_container(&block) + if sub_tree? + content_tag(:ul, capture(&block), class: 'pl-2 tree-border-left') + else + content_tag(:div, class: 'tree_wrapper hide-if-loading') do + content_tag(:ul, capture(&block), class: 'simpleTree root', data: { controller: 'simple-tree', + 'simple-tree-auto-click-value': "#{auto_click?}", + action: 'clicked->history#updateURL' }) + end + end + end + + def auto_click? + @auto_click.to_s + end + + # TDOD check where used + def child_id(child) + child.id.to_s.split('/').last + end + +end + \ No newline at end of file diff --git a/app/components/tree_view_component/tree_view_component.html.haml b/app/components/tree_view_component/tree_view_component.html.haml new file mode 100644 index 000000000..c8b47d1a4 --- /dev/null +++ b/app/components/tree_view_component/tree_view_component.html.haml @@ -0,0 +1,9 @@ +%turbo-frame{id: "#{@id}", **@html_options} + = tree_container do + - children.each do |child| + = child + = content + + + + diff --git a/app/components/widget_block_component.rb b/app/components/widget_block_component.rb new file mode 100644 index 000000000..473cc64fa --- /dev/null +++ b/app/components/widget_block_component.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class WidgetBlockComponent < ViewComponent::Base + + renders_one :help_text + renders_one :widget + + def initialize(id: , title: , description:) + @id = id + @title = title + @description = description + end +end diff --git a/app/components/widget_block_component/widget_block_component.html.haml b/app/components/widget_block_component/widget_block_component.html.haml new file mode 100644 index 000000000..ac4927adc --- /dev/null +++ b/app/components/widget_block_component/widget_block_component.html.haml @@ -0,0 +1,26 @@ +%div.d-flex{data: {turbo: false}} + %div.w-50.mr-3 + %div + %div.card-body + %h5.card-title + = @title + %p.card-text + = @description + + %div.w-100.d-flex.justify-content-center + %div.card.py-3.px-2{style: "width: 600px"} + = render TabsContainerComponent.new(id: @id) do |t| + - t.item(title: 'Widget', selected: true) + - t.item(title: '') do + %span.mx-1 + See code + = inline_svg_tag('json.svg') + + - t.item_content do + %div.py-3.d-flex.justify-content-center + = widget + - t.item_content do + = help_text + +%hr.divider.w-100 + diff --git a/app/controllers/admin/categories_controller.rb b/app/controllers/admin/categories_controller.rb index 8d52212bb..fb4773bca 100644 --- a/app/controllers/admin/categories_controller.rb +++ b/app/controllers/admin/categories_controller.rb @@ -1,5 +1,6 @@ class Admin::CategoriesController < ApplicationController - include SubmissionUpdater + include SubmissionUpdater, TurboHelper + layout :determine_layout before_action :unescape_id, only: [:edit, :show, :update, :destroy] @@ -9,8 +10,7 @@ class Admin::CategoriesController < ApplicationController ATTRIBUTE_TO_INCLUDE = 'name,acronym,created,description,parentCategory,ontologies' def index - response = _categories - render :json => response + @categories = _categories end def new @@ -22,7 +22,8 @@ def new end def edit - @category = LinkedData::Client::Models::Category.find_by_acronym(params[:id], include:'name,acronym,created,description,parentCategory,ontologies' ).first + @category = _category + @acronyms = @category.ontologies.map { |url| url.match(/\/([^\/]+)$/)[1] } @ontologies_category = LinkedData::Client::Models::Ontology.all(include: 'acronym').map {|o|[o.acronym, o.id] } respond_to do |format| format.html { render "edit", :layout => false } @@ -30,7 +31,7 @@ def edit end def create - response = { errors: '', success: '' } + response = { errors: nil, success: '' } start = Time.now begin category = LinkedData::Client::Models::Category.new(values: category_params) @@ -43,35 +44,55 @@ def create rescue Exception => e response[:errors] = "Problem creating the category - #{e.message}" end - render json: response, status: (response[:errors] == '' ? :created : :internal_server_error) + + if response[:errors] + render_turbo_stream alert_error(id: 'category') { response[:errors] } + else + success_message = 'New Category added successfully' + streams = [alert_success(id: 'category') { success_message }] + + streams << prepend('admin_categories_table_body', partial: 'admin/categories/category', locals: { category: category_saved }) + + render_turbo_stream(*streams) + end end def update - response = { errors: '', success: ''} + response = { errors: nil, success: ''} start = Time.now begin - category = LinkedData::Client::Models::Category.find_by_acronym(params[:id], include: ATTRIBUTE_TO_INCLUDE ).first + category = _category add_ontologies_to_object(category_params[:ontologies],category) if (category_params[:ontologies].present? && category_params[:ontologies].size > 0 && category_params[:ontologies].first != '') - delete_ontologies_from_object(category_params[:ontologies],category.ontologies,category) + delete_ontologies_from_object(category_params[:ontologies], category.ontologies,category) category.update_from_params(category_params) - category_update = category.update - if response_error?(category_update) - response[:errors] = response_errors(category_update) + category.ontologies = Array(category_params[:ontologies]) + category_updated = category.update + if response_error?(category_updated) + response[:errors] = response_errors(category_updated) else response[:success] = "category successfully updated in #{Time.now - start}s" end rescue Exception => e response[:errors] = "Problem updating the category - #{e.message}" end - render json: response, status: (response[:errors] == '' ? :ok : :internal_server_error) + + if response[:errors] + render_turbo_stream(alert_error(id: 'category') { response[:errors] }) + else + streams = [alert_success(id: 'category') { response[:success] }, + replace(category.id.split('/').last, partial: 'admin/categories/category', locals: { category: category }) + ] + render_turbo_stream(*streams) + end + end def destroy - response = { errors: '', success: ''} + response = { errors: nil, success: ''} start = Time.now begin - category = LinkedData::Client::Models::Category.find_by_acronym(params[:id]).first + category = _category error_response = category.delete if response_error?(error_response) @@ -82,9 +103,19 @@ def destroy rescue Exception => e response[:errors] = "Problem deleting the category - #{e.message}" end - render json: response, status: (response[:errors] == '' ? :ok : :internal_server_error) + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render turbo_stream: [ + alert(type: 'success') { response[:success] }, + turbo_stream.remove(params[:id]) + ] + end + end + end end - private def unescape_id @@ -96,16 +127,10 @@ def category_params end def _categories - response = { categories: Hash.new, errors: '', success: '' } - start = Time.now - begin - response[:categories] = JSON.parse(LinkedData::Client::HTTP.get(CATEGORIES_URL, { include: ATTRIBUTE_TO_INCLUDE }, raw: true)) + LinkedData::Client::HTTP.get(CATEGORIES_URL, { include: ATTRIBUTE_TO_INCLUDE }) + end - response[:success] = "categories successfully retrieved in #{Time.now - start}s" - LOG.add :debug, "Categories - retrieved #{response[:categories].length} groups in #{Time.now - start}s" - rescue Exception => e - response[:errors] = "Problem retrieving categories - #{e.message}" - end - response + def _category(id = params[:id]) + LinkedData::Client::HTTP.get(CATEGORIES_URL+ "/#{id.split('/').last}", { include: ATTRIBUTE_TO_INCLUDE }) end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 3df27d2e0..d340c3b4a 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -1,15 +1,16 @@ class Admin::GroupsController < ApplicationController - include SubmissionUpdater + include SubmissionUpdater, TurboHelper, AdminHelper layout :determine_layout before_action :unescape_id, only: [:edit, :show, :update, :destroy] before_action :authorize_admin GROUPS_URL = "#{LinkedData::Client.settings.rest_url}/groups" + GROUPS_SYNCHRONIZE_URL = "#{LinkedData::Client.settings.rest_url}/slices/synchronize_groups" + def index - response = _groups - render :json => response + @groups = _groups end def new @@ -30,7 +31,7 @@ def edit end def create - response = { errors: '', success: '' } + response = { errors: nil, success: '' } start = Time.now begin group = LinkedData::Client::Models::Group.new(values: group_params) @@ -43,18 +44,29 @@ def create rescue Exception => e response[:errors] = "Problem creating the group - #{e.message}" end - render json: response, status: (response[:errors] == '' ? :created : :internal_server_error) + + if response[:errors] + render_turbo_stream alert_error(id: 'group') { response[:errors] } + else + success_message = 'New Group added successfully' + streams = [alert_success(id: 'group') { success_message }] + + streams << prepend('admin_groups_table_body', partial: 'admin/groups/group', locals: { group: group_saved }) + + render_turbo_stream(*streams) + end end def update - response = { errors: '', success: ''} + response = { errors: nil, success: ''} start = Time.now begin group = LinkedData::Client::Models::Group.find_by_acronym(params[:id]).first add_ontologies_to_object(group_params[:ontologies],group) if (group_params[:ontologies].present? && group_params[:ontologies].size > 0 && group_params[:ontologies].first != '') delete_ontologies_from_object(group_params[:ontologies],group.ontologies,group) group.update_from_params(group_params) + group.ontologies = Array(group_params[:ontologies]) group_updated = group.update if response_error?(group_updated) response[:errors] = response_errors(group_updated) @@ -64,11 +76,21 @@ def update rescue Exception => e response[:errors] = "Problem updating the group - #{e.message}" end - render json: response, status: (response[:errors] == '' ? :ok : :internal_server_error) + + if response[:errors] + render_turbo_stream(alert_error(id: 'group') { response[:errors] }) + else + + streams = [alert_success(id: 'group') { response[:success] }, + replace(group.id.split('/').last, partial: 'admin/groups/group', locals: { group: group }) + ] + render_turbo_stream(*streams) + end + end def destroy - response = { errors: '', success: ''} + response = { errors: nil, success: ''} start = Time.now begin group = LinkedData::Client::Models::Group.find_by_acronym(params[:id]).first @@ -82,9 +104,51 @@ def destroy rescue Exception => e response[:errors] = "Problem deleting the group - #{e.message}" end - render json: response, status: (response[:errors] == '' ? :ok : :internal_server_error) + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render turbo_stream: [ + alert(type: 'success') { response[:success] }, + turbo_stream.remove(params[:id]) + ] + end + end + end + end + + def synchronize_groups + response = {} + + begin + response_raw = LinkedData::Client::HTTP.get(GROUPS_SYNCHRONIZE_URL, params, raw: true) + + response_json = JSON.parse(response_raw, symbolize_names: true) + + if !response_json.is_a?(Array) && response_json[:errors] + _process_errors(response_json[:errors], response, true) + else + response[:success] = "Synchronization of groups started successfully" + end + rescue JSON::ParserError => e + response[:errors] = "Error parsing JSON response - #{e.class}: #{e.message}" + rescue Exception => e + response[:errors] = "Problem synchronizing groups - #{e.class}: #{e.message}" + end + + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render_turbo_stream alert(type: 'success') { response[:success] } + end + end + end end + private def unescape_id @@ -96,16 +160,6 @@ def group_params end def _groups - response = { groups: Hash.new, errors: '', success: '' } - start = Time.now - begin - response[:groups] = JSON.parse(LinkedData::Client::HTTP.get(GROUPS_URL, { include: 'all' }, raw: true)) - - response[:success] = "groups successfully retrieved in #{Time.now - start}s" - LOG.add :debug, "Groups - retrieved #{response[:groups].length} groups in #{Time.now - start}s" - rescue Exception => e - response[:errors] = "Problem retrieving groups - #{e.message}" - end - response + LinkedData::Client::HTTP.get(GROUPS_URL, { include: 'all' }) end end diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index f182bcd34..4269e1142 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -1,5 +1,5 @@ class AdminController < ApplicationController - include TurboHelper + include TurboHelper, HomeHelper, SparqlHelper layout :determine_layout before_action :cache_setup @@ -10,8 +10,26 @@ class AdminController < ApplicationController PARSE_LOG_URL = lambda { |acronym| "#{ONTOLOGY_URL.call(acronym)}/log" } REPORT_NEVER_GENERATED = "NEVER GENERATED" + def sparql_endpoint + graph = params["named-graph-uri"] + if !session[:user]&.admin? && !graph.blank? + acronym = graph.split('/')[-3] + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first + render(inline: 'Query not permitted') && return if @ontology.nil? || @ontology.errors + end + + response = helpers.ontology_sparql_query(params[:query], graph) + + render inline: response + end + def index @users = LinkedData::Client::Models::User.all + @ontology_visits = ontology_visits_data + @users_visits = user_visits_data + @page_visits = page_visits_data + @ontologies_problems_count = _ontologies_report[:ontologies]&.select{|a,v| v[:problem]}&.size || 0 + if session[:user].nil? || !session[:user].admin? redirect_to :controller => 'login', :action => 'index', :redirect => '/admin' else @@ -19,44 +37,49 @@ def index end end - def update_info - response = {update_info: Hash.new, errors: '', success: '', notices: ''} - json = LinkedData::Client::HTTP.get("#{ADMIN_URL}update_info", params, raw: true) - begin - update_info = JSON.parse(json) + def update_check_enabled + enabled = LinkedData::Client::HTTP.get("#{ADMIN_URL}update_check_enabled", {}, raw: false) - if update_info["error"] - response[:errors] = update_info["error"] - else - response[:update_info] = update_info - response[:notices] = update_info["notes"] if update_info["notes"] - response[:success] = "Update info successfully retrieved" + if enabled + response = {update_info: Hash.new, errors: nil, success: '', notices: ''} + json = LinkedData::Client::HTTP.get("#{ADMIN_URL}update_info", params, raw: true) + + begin + update_info = JSON.parse(json) + + if update_info["error"] + response[:errors] = update_info["error"] + else + response[:update_info] = update_info + response[:notices] = update_info["notes"] if update_info["notes"] + response[:success] = "Update info successfully retrieved" + end + rescue Exception => e + response[:errors] = "Problem retrieving update info - #{e.message}" end - rescue Exception => e - response[:errors] = "Problem retrieving update info - #{e.message}" - end - render :json => response - end - def update_check_enabled - enabled = LinkedData::Client::HTTP.get("#{ADMIN_URL}update_check_enabled", {}, raw: false) - render :json => enabled - end + if response[:errors] + render_turbo_stream alert(id: 'update_check_frame', type: 'danger') { response[:errors] } + else + output = [] - def submissions - @submissions = nil - @acronym = params["acronym"] - @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params["acronym"]).first - begin - submissions = @ontology.explore.submissions - @submissions = submissions.sort {|a,b| b.submissionId <=> a.submissionId } - rescue - @submissions = [] + output << response[:update_info]["notes"] if response[:update_info]["update_available"] + + output << "Current version: #{response[:update_info]['current_version']}" + output << "Appliance ID: #{response[:update_info]['appliance_id']}" + + + render_turbo_stream *output.map{|message| alert(id: 'update_check_frame', type: 'info') {message} } + + + end + else + render_turbo_stream alert(id: 'update_check_frame', type: 'info') { 'not enabled' } end - render :partial => "layouts/ontology_report_submissions" end + def parse_log @acronym = params["acronym"] @parse_log = LinkedData::Client::HTTP.get(PARSE_LOG_URL.call(params["acronym"]), {}, raw: false) @@ -75,7 +98,7 @@ def parse_log end def clearcache - response = {errors: '', success: ''} + response = {errors: nil, success: ''} if @cache.respond_to?(:flush_all) begin @@ -87,11 +110,21 @@ def clearcache else response[:errors] = "The UI cache does not respond to the 'flush_all' command" end - render :json => response + + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render_turbo_stream alert(type: 'success') { response[:success] } + end + end + end + end def resetcache - response = {errors: '', success: ''} + response = {errors: nil, success: ''} if @cache.respond_to?(:reset) begin @@ -103,11 +136,20 @@ def resetcache else response[:errors] = "The UI cache does not respond to the 'reset' command" end - render :json => response + + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render_turbo_stream alert(type: 'success') { response[:success] } + end + end + end end def clear_goo_cache - response = {errors: '', success: ''} + response = {errors: nil, success: ''} begin response_raw = LinkedData::Client::HTTP.post("#{ADMIN_URL}clear_goo_cache", params, raw: true) @@ -115,11 +157,21 @@ def clear_goo_cache rescue Exception => e response[:errors] = "Problem flushing the Goo cache - #{e.class}: #{e.message}" end - render :json => response + + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render_turbo_stream alert(type: 'success') { response[:success] } + end + end + end + end def clear_http_cache - response = {errors: '', success: ''} + response = {errors: nil, success: ''} begin response_raw = LinkedData::Client::HTTP.post("#{ADMIN_URL}clear_http_cache", params, raw: true) @@ -127,7 +179,16 @@ def clear_http_cache rescue Exception => e response[:errors] = "Problem flushing the HTTP cache - #{e.class}: #{e.message}" end - render :json => response + + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render_turbo_stream alert(type: 'success') { response[:success] } + end + end + end end def ontologies_report @@ -161,6 +222,7 @@ def refresh_ontologies_report render :json => response end + def process_ontologies _process_ontologies('enqued for processing', 'processing', :_process_ontology) end @@ -210,11 +272,6 @@ def delete_submission end - def users - response = _users - render :json => response - end - private @@ -301,18 +358,85 @@ def _process_ontologies(success_keyword, error_keyword, process_proc) render :json => response end - def _users - response = {users: Hash.new , errors: '', success: ''} - start = Time.now + + def user_visits_data begin - response[:users] = JSON.parse(LinkedData::Client::HTTP.get(USERS_URL, {include: 'all'}, raw: true)) + analytics = JSON.parse(LinkedData::Client::HTTP.get("#{rest_url}/data/analytics/users", {}, raw: true)) + rescue + analytics = {} + end + visits_data = { visits: [], labels: [] } - response[:success] = "users successfully retrieved in #{Time.now - start}s" - LOG.add :debug, "Users - retrieved #{response[:users].length} users in #{Time.now - start}s" - rescue Exception => e - response[:errors] = "Problem retrieving users - #{e.message}" + return visits_data if analytics.empty? + + analytics.each do |year, year_data| + year_data.each do |month, value| + visits_data[:visits] << value + visits_data[:labels] << DateTime.parse("#{year}/#{month}").strftime("%b %Y") + end end - response + visits_data + end + + def ontology_visits_data + begin + analytics = JSON.parse(LinkedData::Client::HTTP.get("#{rest_url}/data/analytics/ontologies", {}, raw: true)) + rescue + analytics = {} + end + visits_data = { visits: [], labels: [] } + @new_ontologies_count = [] + @ontologies_count = 0 + + return visits_data if analytics.empty? + + aggregated_data = {} + analytics.each do |acronym, years_data| + current_year_count = 0 + previous_year_count = 0 + years_data.each do |year, months_data| + previous_year_count += current_year_count + current_year_count = 0 + aggregated_data[year] ||= {} + months_data.each do |month, value| + if aggregated_data[year][month] + aggregated_data[year][month] += value + else + aggregated_data[year][month] = value + end + current_year_count += value + end + end + @ontologies_count += 1 + if previous_year_count.zero? && current_year_count.positive? + @new_ontologies_count << [acronym] + end + end + + + + aggregated_data.each do |year, year_data| + year_data.each do |month, value| + visits_data[:visits] << value + visits_data[:labels] << DateTime.parse("#{year}/#{month}").strftime("%b %Y") + end + end + visits_data end + def page_visits_data + begin + analytics = JSON.parse(LinkedData::Client::HTTP.get("#{rest_url}/data/analytics/page_visits", {}, raw: true)) + rescue + analytics = {} + end + visits_data = { visits: [], labels: [] } + + return visits_data if analytics.empty? + analytics.each do |path, count| + visits_data[:labels] << path + visits_data[:visits] << count + end + visits_data + end end diff --git a/app/controllers/agents_controller.rb b/app/controllers/agents_controller.rb index d7b6268e6..50c82c7af 100644 --- a/app/controllers/agents_controller.rb +++ b/app/controllers/agents_controller.rb @@ -56,7 +56,7 @@ def create success_message = 'New Agent added successfully' streams = [alert_success(id: alert_id) { success_message }] - streams << prepend('agents_table_content', partial: 'agents/show_line', locals: { agent: new_agent }) + streams << prepend('admin_agents_table_body', partial: 'agents/agent', locals: { agent: new_agent }) streams << replace_agent_form(new_agent, agent_id: nil, frame_id: params[:id], parent_id: parent_id, name_prefix: name_prefix, deletable: deletable @@ -101,7 +101,7 @@ def update table_line_id = agent_table_line_id(agent_id(agent)) agent = LinkedData::Client::Models::Agent.find(agent.id.split('/').last) streams = [alert_success(id: alert_id) { success_message }, - replace(table_line_id, partial: 'agents/show_line', locals: { agent: agent }) + replace(table_line_id, partial: 'agents/agent', locals: { agent: agent }) ] streams << replace_agent_form(agent, agent_id: agent_id(agent.id), name_prefix: params[:name_prefix] , parent_id: parent_id, deletable: deletable) if params[:parent_id] @@ -137,7 +137,7 @@ def update_agent_usages table_line_id = agent_table_line_id(agent_id(agent)) agent.usages = new_usages streams = [alert_success(id: alert_id) { success_message }, - replace(table_line_id, partial: 'agents/show_line', locals: { agent: agent }) + replace(table_line_id, partial: 'agents/agent', locals: { agent: agent }) ] render_turbo_stream(*streams) @@ -171,7 +171,7 @@ def destroy ] else - render alert(type: 'danger') { error } + render_turbo_stream alert(type: 'danger') { error } end end format.html { render json: { success: success_text, error: error } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 02ae47002..e50ff9a09 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -161,12 +161,6 @@ def to_param(name) # Paramaterizes URLs without encoding end end - def undo_param(name) #Undo Paramaterization - unless name.nil? - name.to_s.gsub('_'," ") - end - end - def bp_config_json # For config settings, see # config/bioportal_config.rb @@ -192,22 +186,6 @@ def bp_config_json config.to_json end - def remote_file_exists?(url) - begin - url = URI.parse(url) - - if url.kind_of?(URI::FTP) - check = check_ftp_file(url) - else - check = check_http_file(url) - end - - rescue - return false - end - - check - end def rest_url # Split the URL into protocol and path parts @@ -312,15 +290,6 @@ def redirect_to_home # Redirect to Home Page redirect_to "/" end - def redirect_to_history # Redirects to the correct tab through the history system - if session[:redirect].nil? - redirect_to_home - else - tab = find_tab(session[:redirect][:ontology]) - session[:redirect]=nil - redirect_to uri_url(:ontology=>tab.ontology_id,:conceptid=>tab.concept) - end - end def redirect_new_api(class_view = false) # Hack to make ontologyid and conceptid work in addition to id and ontology params @@ -377,24 +346,6 @@ def authorize_and_redirect end end - # Verifies that a user owns an object - def authorize_owner(id=nil) - if id.nil? - id = params[:id].to_i - end - - id.map! {|i| i.to_i} if id.kind_of?(Array) - - if session[:user].nil? - redirect_to_home - else - if id.kind_of?(Array) - redirect_to_home if !session[:user].admin? && !id.include?(session[:user].id.to_i) - else - redirect_to_home if !session[:user].admin? && !session[:user].id.to_i.eql?(id) - end - end - end def authorize_admin admin = session[:user] && session[:user].admin? @@ -409,41 +360,7 @@ def ontology_restricted?(acronym) restrict_downloads = $NOT_DOWNLOADABLE restrict_downloads.include? acronym end - # updates the 'history' tab with the current selected concept - def update_tab(ontology, concept) - array = session[:ontologies] || [] - found = false - for item in array - if item.ontology_id.eql?(ontology.id) - item.concept=concept - found=true - end - end - - unless found - array << History.new(ontology.id, ontology.name, ontology.acronym, concept) - end - - session[:ontologies]=array - end - - # Removes a 'history' tab - def remove_tab(ontology_id) - array = session[:ontologies] - array.delete(find_tab(ontology_id)) - session[:ontologies]=array - end - # Returns a specific 'history' tab - def find_tab(ontology_id) - array = session[:ontologies] - for item in array - if item.ontology_id.eql?(ontology_id) - return item - end - end - return nil - end def check_delete_mapping_permission(mappings) # ensure mappings is an Array of mappings (some calls may provide only a single mapping instance) @@ -467,13 +384,13 @@ def using_captcha? def get_class(params) lang = request_lang - + if @ontology.flat? ignore_concept_param = params[:conceptid].nil? || - params[:conceptid].empty? || - params[:conceptid].eql?("root") || - params[:conceptid].eql?("bp_fake_root") + params[:conceptid].empty? || + params[:conceptid].eql?("root") || + params[:conceptid].eql?("bp_fake_root") if ignore_concept_param # Don't display any classes in the tree @concept = LinkedData::Client::Models::Class.new @@ -495,24 +412,25 @@ def get_class(params) # not ignoring 'bp_fake_root' here include = 'prefLabel,hasChildren,obsolete' ignore_concept_param = params[:conceptid].nil? || - params[:conceptid].empty? || - params[:conceptid].eql?("root") + params[:conceptid].empty? || + params[:conceptid].eql?("root") if ignore_concept_param # get the top level nodes for the root # TODO_REV: Support views? Replace old view call: @ontology.top_level_classes(view) - @roots = @ontology.explore.roots(concept_schemes: params[:concept_schemes]) + @roots = @ontology.explore.roots(concept_schemes: params[:concept_schemes]) if @roots.nil? || @roots.empty? LOG.add :debug, "Missing @roots for #{@ontology.acronym}" classes = @ontology.explore.classes.collection @concept = classes.first.explore.self(full: true) if classes.first return end - + @root = LinkedData::Client::Models::Class.new(read_only: true) @root.children = @roots.sort{|x,y| (x.prefLabel || "").downcase <=> (y.prefLabel || "").downcase} # get the initial concept to display - root_child = @root.children.first + root_child = @root.children&.first + not_found("Missing roots #{@roots.id}") if root_child.nil? @concept = root_child.explore.self(full: true, lang: lang) # Some ontologies have "too many children" at their root. These will not process and are handled here. @@ -573,17 +491,7 @@ def get_simplified_ontologies_hash() return simple_ontologies end - def get_ontology_details(ont_uri) - # Note the simplify_ontology_model will cache individual ontology data. - begin - ont_model = LinkedData::Client::Models::Ontology.find(ont_uri) - ont = simplify_ontology_model(ont_model) - rescue Exception => e - LOG.add :error, e.message - return nil - end - return ont - end + def simplify_classes(classes) # Simplify the classes batch service data for the UI diff --git a/app/controllers/concepts_controller.rb b/app/controllers/concepts_controller.rb index 79adb6e40..bcb1f3453 100644 --- a/app/controllers/concepts_controller.rb +++ b/app/controllers/concepts_controller.rb @@ -3,6 +3,8 @@ class ConceptsController < ApplicationController include MappingsHelper include ConceptsHelper + include TurboHelper + layout 'ontology' def show_concept @@ -23,7 +25,8 @@ def show_concept @instances_concept_id = @concept.id concept_not_found(params[:id]) if @concept.nil? - gather_details + @notes = @concept.explore.notes + render :partial => 'show' end @@ -40,22 +43,22 @@ def show @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first @ob_instructions = helpers.ontolobridge_instructions_template(@ontology) - if request.xhr? - display = params[:callback].eql?('load') ? {full: true} : {display: "prefLabel"} - @concept = @ontology.explore.single_class(display, params[:id]) - concept_not_found(params[:id]) if @concept.nil? - @schemes = params[:concept_schemes]&.split(',') - show_ajax_request # process an ajax call - else - # Get the latest 'ready' submission, or fallback to any latest submission - # TODO: change the logic here if the fallback will crash the visualization - @submission = get_ontology_submission_ready(@ontology) # application_controller - - @concept = @ontology.explore.single_class({full: true}, params[:id]) - concept_not_found(params[:id]) if @concept.nil? - @schemes = params[:concept_schemes].split(',') - show_ajax_request # process a full call - end + # Get the latest 'ready' submission, or fallback to any latest submission + # TODO: change the logic here if the fallback will crash the visualization + @submission = get_ontology_submission_ready(@ontology) # application_controller + + @concept = @ontology.explore.single_class({full: true}, params[:id]) + concept_not_found(params[:id]) if @concept.nil? + @schemes = params[:concept_schemes].split(',') + + @concept.children = @concept.explore.children(pagesize: 750, concept_schemes: Array(@schemes).join(','), language: request_lang, display: 'prefLabel,obsolete,hasChildren').collection || [] + @concept.children.sort! { |x, y| (x.prefLabel || "").downcase <=> (y.prefLabel || "").downcase } unless @concept.children.empty? + render turbo_stream: [ + replace(helpers.child_id(@concept) + '_open_link') { TreeLinkComponent.tree_close_icon }, + replace(helpers.child_id(@concept) + '_childs') do + helpers.concepts_tree_component(@concept, @concept, @ontology.acronym, Array(@schemes), request_lang, sub_tree: true) + end + ] end def show_label @@ -92,11 +95,16 @@ def show_tree return end @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first - if @ontology.nil? + if @ontology.nil? || @ontology.errors ontology_not_found(params[:ontology]) - else + else get_class(params) #application_controller - render partial: 'ontologies/treeview', locals: { autoCLick: params[:auto_click] || true } + + not_found("Missing roots") if @root.nil? + + render inline: helpers.concepts_tree_component(@root, @concept, + @ontology.acronym, Array(params[:concept_schemes]&.split(',')), request_lang, + id: 'concepts_tree_view', auto_click: params[:auto_click] || true) end end @@ -159,7 +167,8 @@ def details @concept = @ontology.explore.single_class({full: true}, CGI.unescape(params[:conceptid])) concept_not_found(CGI.unescape(params[:conceptid])) if @concept.nil? - + @container_id = params[:modal] ? 'application_modal_content' : 'concept_details' + if params[:styled].eql?("true") render :partial => "details", :layout => "partial" else @@ -178,35 +187,11 @@ def biomixer render partial: "biomixer", layout: false end -# PRIVATE ----------------------------------------- -private - def show_ajax_request - case params[:callback] - when 'children' # Children is called only for drawing the tree - @children = @concept.explore.children(pagesize: 750, concept_schemes: Array(@schemes).join(','), language: request_lang, display: 'prefLabel,obsolete,hasChildren').collection || [] - @children.sort! { |x, y| (x.prefLabel || "").downcase <=> (y.prefLabel || "").downcase } unless @children.empty? - render :partial => 'child_nodes' - end - end + private - # gathers the full set of data for a node - def show_uri_request - gather_details - build_tree - end - def gather_details - @notes = @concept.explore.notes - update_tab(@ontology, @concept.id) #updates the 'history' tab with the current node - end - def build_tree - # find path to root - rootNode = @concept.explore.tree(include: "prefLabel,hasChildren,obsolete,subClassOf") - @root = LinkedData::Client::Models::Class.new(read_only: true) - @root.children = rootNode unless rootNode.nil? - end def filter_concept_with_no_date(concepts) concepts.filter { |c| !concept_date(c).nil?} diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb new file mode 100644 index 000000000..443a07fdd --- /dev/null +++ b/app/controllers/concerns/search_aggregator.rb @@ -0,0 +1,230 @@ +module SearchAggregator + extend ActiveSupport::Concern + BLACKLIST_FIX_STR = [ + "https://", + "http://", + "bioportal.bioontology.org/ontologies/", + "purl.bioontology.org/ontology/", + "purl.obolibrary.org/obo/", + "swrl.stanford.edu/ontologies/", + "mesh.owl" # Avoids RH-MESH subordinate to MESH + ] + + BLACKLIST_REGEX = [ + /abnormalities/i, + /biological/i, + /biology/i, + /bioontology/i, + /clinical/i, + /extension/i, + /\.gov/i, + /ontology/i, + /ontologies/i, + /semanticweb/i + ] + + def aggregate_results(query, results) + ontologies = aggregate_by_ontology(results) + grouped_results = add_subordinate_ontologies(query, ontologies) + all_ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym,name', include_views: true, display_links: false, display_context: false) + + grouped_results.map do |group| + format_search_result(group, all_ontologies) + end + end + + def format_search_result(result, ontologies) + same_ont = result[:same_ont] + same_cls = result[:sub_ont] + result = same_ont.shift + ontology = result.links['ontology'].split('/').last + { + root: search_result_elem(result, ontology, ontology_name_acronym(ontologies, ontology)), + descendants: same_ont.map { |x| search_result_elem(x, ontology, '') }, + reuses: same_cls.map do |x| + format_search_result(x, ontologies) + end + } + end + + private + + def search_result_elem(class_object, ontology_acronym, title) + label = concept_label(class_object.prefLabel) + { + uri: class_object.id.to_s, + title: title.empty? ? label : "#{label} - #{title}", + ontology_acronym: ontology_acronym, + link: "/ontologies/#{ontology_acronym}?p=classes&conceptid=#{class_object.id}", + definition: Array(class_object.definition).join(' ') + } + end + + def concept_label(pref_labels_list, obsolete = false, max_length = 60) + # select closest to query + selected = pref_labels_list.select do |pref_lab| + pref_lab.include?(@search_query) || @search_query.include?(pref_lab) + end.first + + selected ||= (pref_labels_list&.first || '') + + selected = selected[0..max_length] if selected.size > max_length + selected = "#{selected}".html_safe if obsolete + selected + end + + def ontology_name_acronym(ontologies, selected_acronym) + ontology = ontologies.select { |x| x.acronym.eql?(selected_acronym.split('/').last) }.first + binding.pry if ontology.nil? + "#{ontology.name} (#{ontology.acronym})" + end + + def aggregate_by_ontology(results) + ontologies = {} + + results.each do |res| + ont = res.links['ontology'] + unless ontologies[ont] + ontologies[ont] = { + # classes with same URI + same_cls: [], + # other classes from the same ontology + same_ont: [], + # subordinate ontologies + sub_ont: [] + } + end + ontologies[ont][:same_ont] << res + end + ontologies.values + end + + def add_subordinate_ontologies(query, ontologies) + # get for each concept his main ontology parent + concepts_ontology_owner = extract_concepts_owners(ontologies, query) + + # aggregate the subordinate results below the owner ontology results + subordinate_ontologies = [] + ontologies.each_with_index do |ont, i| + cls_id = ont[:same_ont].first["@id"] + + if concepts_ontology_owner.has_key?(cls_id) + # get the ontology that owns this class (if any) + ont_owner = concepts_ontology_owner[cls_id] + if ont_owner[:index].eql?(i) + # the current ontology is the owner of this primary result + subordinate_ontologies.push(ont) + else + # There is an owner, so put this ont result set into the sub_ont array of the owner + real_owner = ontologies[ont_owner[:index]] + real_owner[:sub_ont].push(ont) + end + else + # There is no ontology that owns this primary class result, just + # display this at the top level (it's not a subordinate) + subordinate_ontologies.push(ont) + end + end + subordinate_ontologies + end + + def extract_concepts_owners(ontologies, query) + cls_ont_owner_tracker = {} + ontologies.each do |ont| + ont[:sub_ont] = [] # array for any subordinate ontology results regrouping the concept reuses + + cls_id = ont[:same_ont].first["@id"] + next if cls_ont_owner_tracker.has_key?(cls_id) + + # find the best match for the ontology owner (must iterate over all acronyms) + ont_owner = ontology_owner_of_class(cls_id, ontologies, query) + + # This primary class result is owned by an ontology + cls_ont_owner_tracker[cls_id] = ont_owner if ont_owner[:index] + end + cls_ont_owner_tracker + end + + def extract_back_list_words(acronyms, query) + blacklist_words = [] + query.split(/\s+/).each_with_index do |search_word, i| + # Convert blacklist_search_words_arr to regex constructs so they are removed + # with case-insensitive matches in blacklist_cls_id_components + blacklist_words.push(Regexp.new(search_word, Regexp::IGNORECASE)) + + # Check for any substring matches against ontology acronyms, where the + # acronyms are assumed to be upper case strings. + # Note: We cannot use the ont_acronyms array .index method because it doesn't search for substring matches. + search_token = search_word + match = false + + acronyms.each do |acronym| + match = acronym.include?(search_token) + break if match + end + + # Remove this blacklisted search token because it matches or partially matches an ontology acronym. + blacklist_words.delete_at(i) if match + end + blacklist_words + end + + def ontology_owner_of_class(cls_id, ontologies, query) + acronyms = ontologies.map { |ont| ont[:same_ont].first.links['ontology'].split('/').last } + + # Remove any items in blacklistSearchWordsArr that match ontology acronyms. + # TODO make sure this is really useful + blacklist_words = extract_back_list_words(acronyms, query) + + ont_owner = { + acronym: "", + index: nil, + weight: 0 + } + + acronyms.each_with_index do |acronym, i| + if ontology_own_class?(cls_id, acronym, blacklist_words) + weight = acronym.size * (cls_id.upcase.rindex(acronym) + 1) + if weight > ont_owner[:weight] + ont_owner = { + acronym: acronym, + index: i, + weight: weight + } + # Cannot break here, in case another acronym has greater weight. + end + end + end + + ont_owner + end + + def ontology_own_class?(cls_id, acronym, blacklist_words) + cls_id = blacklist_cls_id_components(cls_id.dup, blacklist_words) + + cls_id.upcase.include?(acronym) rescue binding.pry + end + + def blacklist_cls_id_components(cls_id, blacklist_words) + + stripped_id = cls_id + + # Remove fixed strings first + BLACKLIST_FIX_STR.each do |fixed_str| + stripped_id.gsub!(fixed_str, "") + end + + # Cleanup with regex replacements + BLACKLIST_REGEX.each do |regex| + stripped_id.gsub!(regex, "") + end + + # Remove search keywords (see perform_search and aggregate_results_with_subordinate_ontologies) + blacklist_words.each do |search_word_regex| + stripped_id.gsub!(search_word_regex, "") + end + + stripped_id + end +end + diff --git a/app/controllers/history_controller.rb b/app/controllers/history_controller.rb deleted file mode 100644 index 687839df5..000000000 --- a/app/controllers/history_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -class HistoryController < ApplicationController - - def remove # removes a 'history' tab - remove_tab(undo_param(params[:ontology])) - render :text =>"success" - end - - def update # updates the 'history' tab to point to the new node - ontology = DataAccess.getOntology(params[:ontology]) - update_tab(ontology,params[:concept]) - render :text =>"success" - end - - -end diff --git a/app/controllers/instances_controller.rb b/app/controllers/instances_controller.rb index 5d346ef3b..8b170f5e2 100644 --- a/app/controllers/instances_controller.rb +++ b/app/controllers/instances_controller.rb @@ -2,18 +2,20 @@ class InstancesController < ApplicationController include InstancesHelper def index_by_ontology get_ontology(params) - custom_render get_instances_by_ontology_json(@ontology, get_query_parameters) + instances = get_instances_by_ontology_json(@ontology, get_query_parameters) + custom_render(instances, @ontology.acronym) end def index_by_class get_ontology(params) get_class(params) - custom_render get_instances_by_class_json(@concept, get_query_parameters) + custom_render(get_instances_by_class_json(@concept, get_query_parameters), @ontology.acronym) end def show - @instance = get_instance_details_json(params[:ontology_id], params[:instance_id], {include: 'all'}) - render partial: 'instances/instance_details' + @instance = get_instance_details_json(params[:ontology_id], params[:id] || params[:instance_id], {include: 'all'}) + + render partial: 'instances/instance_details', layout: nil end private @@ -23,8 +25,8 @@ def get_ontology(params) ontology_not_found(params[:ontology]) if @ontology.nil? end # json render + adding next and prev pages links - def custom_render(instances) - instances[:collection].map! { |i| add_labels_to_print(i, @ontology.acronym)} + def custom_render(instances, ontology_acronym) + instances[:collection].map! { |i| add_labels_to_print(i, ontology_acronym)} if (instances.respond_to? :links) && (!instances.respond_to? :errors) instances.links = { nextPage: get_page_link(instances.nextPage), diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb index b8baeda3a..269fafebe 100644 --- a/app/controllers/mappings_controller.rb +++ b/app/controllers/mappings_controller.rb @@ -117,7 +117,7 @@ def show_mappings end else ontology_acronym = @ontology.acronym - @ontology_name = @ontology.name + @ontology_name = ontology_acronym end if @target_ontology.nil? if params[:target] == EXTERNAL_MAPPINGS_GRAPH @@ -129,12 +129,13 @@ def show_mappings end else target_acronym = @target_ontology.acronym - @target_ontology_name = @target_ontology.name + @target_ontology_name = target_acronym end ontologies = [ontology_acronym, target_acronym] @mapping_pages = LinkedData::Client::HTTP.get("#{MAPPINGS_URL}", { page: page, ontologies: ontologies.join(',') }) + not_found(@mapping_pages.errors) if @mapping_pages.respond_to?(:errors) @mappings = @mapping_pages.collection @delete_mapping_permission = check_delete_mapping_permission(@mappings) @@ -248,7 +249,7 @@ def destroy ] else - render alert(type: 'danger') { error } + render_turbo_stream alert(type: 'danger') { error } end end format.html { render json: { success: success_text, error: error } } diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index 31170cffd..f6469e280 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -10,6 +10,7 @@ class OntologiesController < ApplicationController include MappingStatistics include OntologyUpdater include TurboHelper + include SparqlHelper include SubmissionFilter require 'multi_json' @@ -22,7 +23,7 @@ class OntologiesController < ApplicationController before_action :authorize_and_redirect, :only => [:edit, :update, :create, :new] before_action :submission_metadata, only: [:show] - KNOWN_PAGES = Set.new(["terms", "classes", "mappings", "notes", "widgets", "summary", "properties", "instances", "schemes", "collections"]) + KNOWN_PAGES = Set.new(["terms", "classes", "mappings", "notes", "widgets", "summary", "properties", "instances", "schemes", "collections", "sparql"]) EXTERNAL_MAPPINGS_GRAPH = "http://data.bioontology.org/metadata/ExternalMappings" INTERPORTAL_MAPPINGS_GRAPH = "http://data.bioontology.org/metadata/InterportalMappings" @@ -208,6 +209,14 @@ def collections end end + + def sparql + if request.xhr? + render partial: 'ontologies/sections/sparql', layout: false + else + render partial: 'ontologies/sections/sparql', layout: 'ontology_viewer' + end + end # GET /ontologies/ACRONYM # GET /ontologies/1.xml def show @@ -240,7 +249,7 @@ def show # Note: find_by_acronym includes ontology views @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first - ontology_not_found(params[:ontology]) if @ontology.nil? + ontology_not_found(params[:ontology]) if @ontology.nil? || @ontology.errors # Handle the case where an ontology is converted to summary only. # See: https://github.com/ncbo/bioportal_web_ui/issues/133. @@ -288,6 +297,8 @@ def show self.schemes when 'collections' self.collections + when 'sparql' + self.sparql else self.summary end @@ -387,7 +398,6 @@ def widgets end end - def show_additional_metadata @metadata = submission_metadata @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:id]).first diff --git a/app/controllers/properties_controller.rb b/app/controllers/properties_controller.rb index 0363649c9..7784c5eb7 100644 --- a/app/controllers/properties_controller.rb +++ b/app/controllers/properties_controller.rb @@ -1,8 +1,56 @@ class PropertiesController < ApplicationController - def show - @property = LinkedData::Client::HTTP.get("/ontologies/#{params[:acronym]}/properties/#{helpers.encode_param(params[:id])}") + include TurboHelper - @acronym = params[:acronym] - render partial: 'show' + def show + @property = get_property(params[:id]) + @acronym = params[:acronym] + render partial: 'show' + end + + def show_tree + @ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first + ontology_not_found(params[:ontology]) if @ontology.nil? + @root = OpenStruct.new({children: property_roots(params[:ontology])}) + not_found(@root.children.errors.join) if @root.children.respond_to?(:errors) + + if params[:propertyid] + @property = get_property(params[:propertyid]) + else + @property ||= @root.children.first end + + render inline: helpers.property_tree_component(@root, @property, + @ontology.acronym, request_lang, + id: 'properties_tree_view', auto_click: true) + end + + + def show_children + acronym = params[:ontology] + id = params[:propertyid] + @property = get_property(id, acronym) + @property.children = property_children(id, acronym) + + render turbo_stream: [ + replace(helpers.child_id(@property) + '_open_link') { TreeLinkComponent.tree_close_icon }, + replace(helpers.child_id(@property) + '_childs') do + helpers.property_tree_component(@property, @property, acronym, request_lang, sub_tree: true) + end + ] + end + + + private + def get_property(id, acronym = params[:acronym]) + LinkedData::Client::HTTP.get("/ontologies/#{acronym}/properties/#{helpers.encode_param(id)}") + end + + def property_roots(acronym = params[:acronym]) + LinkedData::Client::HTTP.get("/ontologies/#{acronym}/properties/roots") + end + + def property_children(id, acronym = params[:acronym]) + LinkedData::Client::HTTP.get("/ontologies/#{acronym}/properties/#{helpers.encode_param(id)}/children?display=all") + end + end diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb deleted file mode 100644 index c36ef5b7b..000000000 --- a/app/controllers/reviews_controller.rb +++ /dev/null @@ -1,90 +0,0 @@ -class ReviewsController < ApplicationController - - layout 'ontology_viewer' - - RATING_TYPES = [ - :usabilityRating, - :coverageRating, - :qualityRating, - :formalityRating, - :correctnessRating, - :documentationRating - ].freeze - - def new - @rating_types = RATING_TYPES - @ontology = LinkedData::Client::Models::Ontology.find(params[:ontology]) - @review = LinkedData::Client::Models::Review.new(values: {ontologyReviewed: @ontology.id, creator: session[:user].id}) - - if request.xhr? - render layout: false - end - end - - # GET /reviews/1/edit - def edit - @review = Review.find(params[:id]) - @rating_types = RatingType.all - - if request.xhr? - render layout: false - end - end - - def create - @review = LinkedData::Client::Models::Review.new(values: params[:review]) - @ontology = LinkedData::Client::Models::Ontology.find(@review.ontologyReviewed) - @review_saved = @review.save - if response_error?(@review_saved) - @errors = response_errors(@review_saved) - render :action => "new" - else - respond_to do |format| - format.html do - flash[:notice] = 'Review was successfully created' - redirect_to "/ontologies/#{@ontology.acronym}?p=summary" - end - format.js do - render json: {} - end - end - end - end - - # PUT /reviews/1 - # PUT /reviews/1.xml - def update - @review = Review.find(params[:id]) - ratings = Hash[*(@review.ratings.map{|rate| [rate.id.to_i, rate] }.flatten)] - #puts ratings.inspect - for rating_key in params.keys - if rating_key.include?("star") - #puts rating_key.split("_")[1].to_i - ratings[rating_key.split("_")[1].to_i].value=params[rating_key].to_i - ratings[rating_key.split("_")[1].to_i].save - end - end - if @review.update_attributes(params[:review]) - @review.reload - if request.xhr? - render :action=>'show', :layout=>false - else - redirect_to reviews(:ontology=>review.ontology_id) - end - else - render :action => "edit" - end - end - - # DELETE /reviews/1 - # DELETE /reviews/1.xml - def destroy - @review = Review.find(params[:id]) - @review.destroy - - respond_to do |format| - format.html { redirect_to(reviews_url) } - format.xml { head :ok } - end - end -end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 6b35a2b26..8ba41c4a7 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,14 +1,25 @@ require 'uri' class SearchController < ApplicationController - + include SearchAggregator skip_before_action :verify_authenticity_token layout :determine_layout def index - @search_query = params[:query].nil? ? params[:q] : params[:query] - @search_query ||= "" + @search_query = params[:query] || params[:q] || '' + params[:query] = nil + @advanced_options_open = false + @search_results = [] + + return if @search_query.empty? + + params[:pagesize] = "150" + params[:ontologies] = params[:ontologies_list]&.join(",") + results = LinkedData::Client::Models::Class.search(@search_query, params).collection + + @advanced_options_open = !search_params_empty? + @search_results = aggregate_results(@search_query, results) end def json_search @@ -18,13 +29,18 @@ def json_search end check_params_query(params) check_params_ontologies(params) # Filter on ontology_id + if params["id"]&.eql?('All') + params.delete("id") + params.delete("ontologies") + end search_page = LinkedData::Client::Models::Class.search(params[:q], params) @results = search_page.collection response = "" obsolete_response = "" separator = (params[:separator].nil?) ? "~!~" : params[:separator] - for result in @results + + for result in Array(@results) # TODO_REV: Format the response with type information, target information # record_type = format_record_type(result[:recordType], result[:obsolete]) record_type = "" @@ -32,20 +48,21 @@ def json_search target_value = result.prefLabel.select{|x| x.include?( params[:q].delete('*'))}.first || result.prefLabel.first case params[:target] - when "name" - target_value = result.prefLabel - when "shortid" - target_value = result.id - when "uri" - target_value = result.id + when "name" + target_value = result.prefLabel + when "shortid" + target_value = result.id + when "uri" + target_value = result.id end + acronym = result.links["ontology"].split('/').last json = [] json << "#{target_value}" json << " [obsolete]" if result.obsolete? # used by JS in ontologies/visualize to markup obsolete classes json << "|#{result.id}" json << "|#{record_type}" - json << "|#{result.explore.ontology.acronym}" + json << "|#{acronym}" json << "|#{result.id}" # Duplicated because we used to have shortId and fullId json << "|#{target_value}" # This is nasty, but hard to workaround unless we rewrite everything (form_autocomplete, jump_to, crossdomain_autocomplete) @@ -55,8 +72,8 @@ def json_search if params[:id] && params[:id].split(",").length == 1 json << "|#{CGI.escape((result.definition || []).join(". "))}#{separator}" else - json << "|#{result.explore.ontology.name}" - json << "|#{result.explore.ontology.acronym}" + json << "|#{acronym}" + json << "|#{acronym}" json << "|#{CGI.escape((result.definition || []).join(". "))}#{separator}" end @@ -104,21 +121,17 @@ def check_params_ontologies(params) end end - def format_record_type(record_type, obsolete = false) - case record_type - when "apreferredname" - record_text = "Preferred Name" - when "bconceptid" - record_text = "Class ID" - when "csynonym" - record_text = "Synonym" - when "dproperty" - record_text = "Property" - else - record_text = "" - end - record_text = "Obsolete Class" if obsolete - record_text + def search_params + [ + :ontologies, :categories, + :also_search_properties, :also_search_obsolete, :also_search_views, + :require_exact_match, :require_definition + ] + end + + def search_params_empty? + (params[:lang].nil? || params[:lang].eql?('all')) && + search_params.all?{|key| params[key].nil? || params[key].empty?} end end diff --git a/app/controllers/statistics_controller.rb b/app/controllers/statistics_controller.rb new file mode 100644 index 000000000..ac3828f02 --- /dev/null +++ b/app/controllers/statistics_controller.rb @@ -0,0 +1,14 @@ +class StatisticsController < ApplicationController + include StatisticsHelper, ComponentsHelper + + layout :determine_layout + + def index + projects = LinkedData::Client::Models::Project.all({include: 'created'}) + users = LinkedData::Client::Models::User.all({include: 'created'}) + year_month_count, @year_month_visits = ontologies_by_year_month + @merged_data = merge_time_evolution_data([group_by_year_month(users), + group_by_year_month(projects), + year_month_count]) + end +end diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 3e3fc0972..92380888c 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -17,7 +17,7 @@ def index .sort {|a,b| b.submissionId.to_i <=> a.submissionId.to_i } || [] LOG.add :error, "No submissions for ontology: #{@ontology.id}" if @submissions.empty? - + render :index, layout: nil end # When getting "Add submission" form to display diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index dc6c75bdb..53b70ea16 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -5,6 +5,21 @@ class UsersController < ApplicationController before_action :authorize_admin, only: [:index,:subscribe, :un_subscribe] layout :determine_layout + include TurboHelper + + + def index + + onts = LinkedData::Client::Models::Ontology.all(include: 'administeredBy') + projects = LinkedData::Client::Models::Project.all(include: 'creator') + + @users = LinkedData::Client::Models::User.all(include: 'all') + @users.each do |user| + user.ontologies = onts.select {|o| o.administeredBy.include? user.id } + user.project = projects.select {|p| p.creator.include? user.id } + end + + end # GET /users/1 # GET /users/1.xml @@ -112,7 +127,7 @@ def update # DELETE /users/1 def destroy - response = {errors: '', success: ''} + response = {errors: nil, success: ''} @user = LinkedData::Client::Models::User.find(params[:id]) @user = LinkedData::Client::Models::User.find_by_username(params[:id]).first if @user.nil? if(session[:user].admin?) @@ -123,7 +138,18 @@ def destroy response[:errors] << 'Not permitted ' end - render json: response + respond_to do |format| + format.turbo_stream do + if response[:errors] + render_turbo_stream alert(type: 'danger') { response[:errors].to_s } + else + render turbo_stream: [ + alert(type: 'success') { response[:success] }, + turbo_stream.remove(params[:id]) + ] + end + end + end end def custom_ontologies diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 7553a567d..17f172ff7 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -3,4 +3,23 @@ def selected_admin_section?(section_title) current_section = params[:section] || 'site' current_section.eql?(section_title) end + + + def new_ontologies_created_title + content_tag(:div, + "The following ontologies: #{@new_ontologies_count.join(', ')} were created in this year", + style: 'width: 400px; max-height: 300px') + end + + def visits_evolution + return 0 if @users_visits[:visits].empty? + + @users_visits[:visits].last - @users_visits[:visits][-2] + end + + def action_button(name, link, method: :post, class_style: 'btn btn-link mb-3') + button_to name, link, method: method, class: class_style, + form: {data: { turbo: true, turbo_confirm: "Are you sure you want to #{name}?", turbo_frame: '_top'}} + + end end diff --git a/app/helpers/agent_helper.rb b/app/helpers/agent_helper.rb index f583c2cf5..802dc1087 100644 --- a/app/helpers/agent_helper.rb +++ b/app/helpers/agent_helper.rb @@ -165,31 +165,45 @@ def agent_tooltip(agent) email = agent.email type = agent.agentType identifiers = display_identifiers(agent.identifiers, link: false) - #binding.pry + identifiers = orcid_number(identifiers) if agent.affiliations && agent.affiliations != [] - affiliations = "affiliations: " + affiliations = "" agent.affiliations.each do |affiliation| - affiliations = affiliations + affiliation.name + ". " + affiliations = affiliations + affiliation.acronym + " " end end person_icon = inline_svg_tag 'icons/person.svg' , class: 'agent-type-icon' organization_icon = inline_svg_tag 'icons/organization.svg', class: 'agent-type-icon' + ror_icon = inline_svg_tag 'icons/ror.svg', class: 'agent-dependency-icon ror' + orcid_icon = inline_svg_tag 'icons/orcid.svg', class: 'agent-dependency-icon' agent_icon = type == "organization" ? organization_icon : person_icon - tooltip_html = generate_agent_tooltip(agent_icon, name, email, identifiers, affiliations) + identifiers_icon = type == "organization" ? ror_icon : orcid_icon + tooltip_html = generate_agent_tooltip(agent_icon, name, email, identifiers, affiliations, identifiers_icon) return tooltip_html end - def generate_agent_tooltip(agent_icon, name, email = nil, identifiers = nil, affiliations = nil) + def generate_agent_tooltip(agent_icon, name, email = nil, identifiers = nil, affiliations = nil, identifiers_icon = nil) content_tag(:div, class: 'agent-container') do content_tag(:div, agent_icon, class: 'agent-circle') + content_tag(:div) do content_tag(:div, name, class: 'agent-name') + content_tag(:div, email || '', class: 'agent-dependency') + - content_tag(:div, identifiers || '', class: 'agent-dependency') + - content_tag(:div, affiliations || '', class: 'agent-dependency') + unless identifiers.to_s.empty? + content_tag(:div, class: 'agent-dependency') do + identifiers_icon + + identifiers || '' + end + end + + unless affiliations.to_s.empty? + content_tag(:div, class: 'agent-dependency') do + inline_svg_tag('icons/organization.svg', class: 'agent-dependency-icon') + + affiliations || '' + end + end end end end + def agent_chip_component(agent) person_icon = inline_svg_tag 'icons/person.svg' , class: 'agent-type-icon' @@ -217,6 +231,10 @@ def render_chip_component(title,agent_icon,name) end end + def orcid_number(orcid) + return orcid.split("/").last + end + end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c29cd7c35..507078a0e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -25,11 +25,8 @@ module ApplicationHelper def ontologies_analytics - LinkedData::Client::Analytics.all.to_h.map do |key, ontology_analytics| - next if key.eql?(:links) || key.eql?(:context) - - [key.to_s, ontology_analytics.to_h.values.map { |x| x&.values }.flatten.compact.sum] - end.compact.to_h + data = LinkedData::Client::Analytics.last_month.onts + data.map{|x| [x[:ont].to_s, x[:views]]}.to_h end def get_apikey @@ -63,6 +60,7 @@ def isOwner?(id) end end end + def encode_param(string) @@ -105,146 +103,9 @@ def current_user_admin? session[:user] && session[:user].admin? end - def draw_note_tree(notes,key) - output = "" - draw_note_tree_leaves(notes,0,output,key) - return output - end - - def draw_note_tree_leaves(notes,level,output,key) - for note in notes - name="Anonymous" - unless note.user.nil? - name=note.user.username - end - headertext="" - notetext="" - if note.note_type.eql?(5) - headertext<< "
          " - notetext << " - #{note.comment}" - else - headertext<< "
          " - notetext<< "#{simple_format(note.comment)}" - end - - - output << " -
          -
          - #{headertext} -
          - #{name} at #{note.created_at.strftime('%m/%d/%y %H:%M')} -
          #{note.type_label.titleize}: #{note.subject}
          -
          -
          - -
          - -
          -
          -
          -
          - #{notetext}" - if session[:user].nil? - output << "" - else - if @modal - output << "" - else - output << "" - end - end - output << "
          -
          -
          - -
          -
          -
          " - if(!note.children.nil? && note.children.size>0) - draw_note_tree_leaves(note.children,level+1,output,key) - end - end - end - - def draw_tree(root, acronym, id = nil, concept_schemes = nil) - id = root.children.first.id if id.nil? - - # TODO: handle tree view for obsolete classes, e.g. 'http://purl.obolibrary.org/obo/GO_0030400' - raw build_tree(root, '', id, acronym, concept_schemes: concept_schemes) - end - - def build_tree(node, string, id, acronym, concept_schemes: nil) - - return string if node.children.nil? || node.children.empty? - - node.children.sort! { |a, b| (main_language_label(a.prefLabel) || a.id).downcase <=> (main_language_label(a.prefLabel) || b.id).downcase } - node.children.each do |child| - active_style = child.id.eql?(id) ? "active" : '' - - # This fake root will be present at the root of "flat" ontologies, we need to keep the id intact - - if child.id.eql?('bp_fake_root') - string << tree_link_to_concept(child: child, ontology_acronym: acronym, - active_style: active_style, node: node, skos: !concept_schemes.nil?) - else - string << tree_link_to_concept(child: child, ontology_acronym: acronym, - active_style: active_style, node: node, skos: !concept_schemes.nil?) - if child.hasChildren && !child.expanded? - string << tree_link_to_children(child: child, acronym: acronym, concept_schemes: concept_schemes) - elsif child.expanded? - string << '
            ' - build_tree(child, string, id, acronym, concept_schemes: concept_schemes) - string << '
          ' - end - string << '' - end - end - string - end - - def tree_link_to_concept(child:, ontology_acronym:, active_style:, node: nil, skos: false) - language = request_lang - li_id = child.id.eql?('bp_fake_root') ? 'bp_fake_root' : short_uuid - open = child.expanded? ? "class='open'" : '' - #icons = child.relation_icon(node) removed because slow - muted_style = skos && Array(child.isInActiveScheme).empty? ? 'text-muted' : nil - muted_title = muted_style && !child.obsolete? ? "title='is not in a scheme'" : nil - href = ontology_acronym.blank? ? '#' : "/ontologies/#{ontology_acronym}/concepts/?id=#{CGI.escape(child.id)}&language=#{language}" - - if child.prefLabel.nil? - pref_label_html = child.id.split('/').last - else - pref_label_lang, pref_label_html = select_language_label(child.prefLabel) - pref_label_lang = pref_label_lang.to_s.upcase - tooltip = pref_label_lang.eql?("@NONE") ? "" : "data-controller='tooltip' data-tooltip-position-value='right' title='#{pref_label_lang}'"; - end - - link = <<-EOS - - #{ pref_label_html } - - EOS - "
        • #{link}" - end - - - def tree_link_to_children(child:, acronym: ,concept_schemes: nil) - language = request_lang - li_id = child.id.eql?('bp_fake_root') ? 'bp_fake_root' : short_uuid - concept_schemes = "&concept_schemes=#{concept_schemes.map{|x| CGI.escape(x)}.join(',')}" if concept_schemes - - link = "ajax_class" - "
          • #{link}
          " + def child_id(child) + child.id.to_s.split('/').last end def loading_spinner(padding = false, include_text = true) @@ -256,11 +117,7 @@ def loading_spinner(padding = false, include_text = true) end end - # This gives a very hacky short code to use to uniquely represent a class - # based on its parent in a tree. Used for unique ids in HTML for the tree view - def short_uuid - rand(36**8).to_s(36) - end + def help_icon(link, html_attribs = {}) html_attribs["title"] ||= "Help" @@ -380,17 +237,7 @@ def get_groups_data(groups = nil) @groups_for_js = @groups_map.to_json end - def metadata_for_select - get_metadata - return @metadata_for_select - end - def get_metadata - @metadata_for_select = [] - submission_metadata.each do |data| - @metadata_for_select << data["attribute"] - end - end def ontologies_to_acronyms(ontologyIDs) @@ -405,6 +252,8 @@ def at_slice? !@subdomain_filter.nil? && !@subdomain_filter[:active].nil? && @subdomain_filter[:active] == true end + + # TODO this helper is not used but can be usefully def truncate_with_more(text, options = {}) length ||= options[:length] ||= 30 trailing_text ||= options[:trailing_text] ||= " ... " diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb index c67209696..c1aa0e1fb 100644 --- a/app/helpers/components_helper.rb +++ b/app/helpers/components_helper.rb @@ -1,4 +1,44 @@ module ComponentsHelper + + def tree_component(root, selected, target_frame:, sub_tree: false, id: nil, auto_click: false, &child_data_generator) + root.children.sort! { |a, b| (a.prefLabel || a.id).downcase <=> (b.prefLabel || b.id).downcase } + + render TreeViewComponent.new(id: id, sub_tree: sub_tree, auto_click: auto_click) do |tree_child| + root.children.each do |child| + children_link, data, href = child_data_generator.call(child) + + if children_link.nil? || data.nil? || href.nil? + raise ArgumentError, "child_data_generator block did not provide all the child arguements" + end + + tree_child.child(child: child, href: href, + children_href: children_link, selected: child.id.eql?(selected&.id), + muted: child.isInActiveScheme&.empty?, + target_frame: target_frame, + data: data) do + tree_component(child, selected, target_frame: target_frame, sub_tree: true, + id: id, auto_click: auto_click, &child_data_generator) + end + end + end + end + + + + + def chart_component(title: '', type: , labels: , datasets: , index_axis: 'x', show_legend: false) + data = { + controller: 'load-chart', + 'load-chart-type-value': type, + 'load-chart-title-value': title, + 'load-chart-labels-value': labels, + 'load-chart-index-axis-value': index_axis, + 'load-chart-datasets-value': datasets, + 'load-chart-legend-value': show_legend, + } + content_tag(:canvas, nil, data: data) + end + def info_tooltip(text) render Display::InfoTooltipComponent.new(text: text) end diff --git a/app/helpers/concepts_helper.rb b/app/helpers/concepts_helper.rb index 4418e6d52..15ca7c4ba 100644 --- a/app/helpers/concepts_helper.rb +++ b/app/helpers/concepts_helper.rb @@ -1,5 +1,31 @@ # frozen_string_literal: true module ConceptsHelper + + def concept_link(acronym, child, language) + child.id.eql?('bp_fake_root') ? '#' : "/ontologies/#{acronym}/concepts/?id=#{CGI.escape(child.id)}&language=#{language}" + end + + def concept_children_link(acronym, child, language, concept_schemes) + "/ajax_concepts/#{acronym}/?conceptid=#{CGI.escape(child.id)}&concept_schemes=#{concept_schemes.join(',')}&language=#{language}" + end + + def concept_tree_data(acronym, child, language, concept_schemes) + href = concept_link(acronym, child, language) + children_link = concept_children_link(acronym, child, language, concept_schemes) + data = { + conceptid: child.id, + 'active-collections-value': child.isInActiveCollection || [], + 'collections-value': child.memberOf || [], + 'skos-collection-colors-target': 'collection', + } + [children_link, data, href] + end + def concepts_tree_component(root, selected_concept, acronym, concept_schemes, language, sub_tree: false, id: nil, auto_click: false) + tree_component(root, selected_concept, target_frame: 'concept_show', sub_tree: sub_tree, id: id, auto_click: auto_click) do |child| + concept_tree_data(acronym, child, language, concept_schemes) + end + end + def exclude_relation?(relation_to_check, ontology = nil) excluded_relations = %w[type rdf:type [R] SuperClass InstanceCount] @@ -39,7 +65,7 @@ def sub_menu_active_class(section) def default_sub_menu? !sub_menu_active?('list') && !sub_menu_active?('date') end - + def default_sub_menu_class "active show" if default_sub_menu? end @@ -79,22 +105,25 @@ def same_period?(year, month, date) year.eql?(date.year) && month.eql?(date.strftime('%B')) end - def concepts_li_list(concepts) + def concepts_li_list(concepts, auto_click: false) out = '' concepts.each do |concept| - out += tree_link_to_concept(child: concept, ontology_acronym: @ontology.acronym, active_style: '') + children_link, data, href = concept_tree_data(@ontology.acronym, concept, request_lang, []) + out += render TreeLinkComponent.new(child: concept, href: href, + children_href: '#', selected: concept.id.eql?(concepts.first.id) && auto_click, + target_frame: 'concept_show', data: data) end out end - def render_concepts_by_dates - return if @concepts_year_month.empty? + def render_concepts_by_dates(auto_click: false) + return if @concepts_year_month.empty? first_year, first_month_concepts = @concepts_year_month.shift first_month, first_concepts = first_month_concepts.shift out = '' if same_period?(first_year, first_month, @last_date) - out += "
            #{concepts_li_list(first_concepts)}
          " + out += "
            #{concepts_li_list(first_concepts, auto_click: auto_click)}
          " else tmp = {} tmp[first_month] = first_concepts @@ -107,7 +136,7 @@ def render_concepts_by_dates @concepts_year_month.each do |year, month_concepts| month_concepts.each do |month, concepts| out += "
            #{month + ' ' + year.to_s}" - out += concepts_li_list(concepts) + out += concepts_li_list(concepts, auto_click: auto_click) out += "
          " end end diff --git a/app/helpers/instances_helper.rb b/app/helpers/instances_helper.rb index ec19cd36f..bb7309711 100644 --- a/app/helpers/instances_helper.rb +++ b/app/helpers/instances_helper.rb @@ -73,7 +73,9 @@ def instance_property_value(property, ontology_acronym) def add_labels_to_print(instance, ontology_acronym) instance['labelToPrint'] = instance_label(instance) - instance['types'].map!{ |t| {type:t, labelToPrint:concept_label(ontology_acronym , t)}} + instance['types'].reject!{|t| t['NamedIndividual']} + instance['types'].map!{ |t| {type:t, labelToPrint: t}} + instance['ontology'] = ontology_acronym instance end diff --git a/app/helpers/modal_helper.rb b/app/helpers/modal_helper.rb index 568c0f866..fb2e0cd58 100644 --- a/app/helpers/modal_helper.rb +++ b/app/helpers/modal_helper.rb @@ -21,7 +21,7 @@ def modal_frame_container(id = 'application_modal') def render_in_modal(id = 'application_modal', &block) render TurboFrameComponent.new(id: "#{id}_content") do - block.call.html_safe if block_given? + capture(&block).html_safe if block_given? end end @@ -34,6 +34,7 @@ def modal_controller_data(html_options) action: 'click->show-modal#show' } + html_options ||= {data: {}} html_options[:data].merge!(new_data) do |_, old, new| "#{old} #{new}" end diff --git a/app/helpers/multi_languages_helper.rb b/app/helpers/multi_languages_helper.rb index f1c0f8364..81d605818 100644 --- a/app/helpers/multi_languages_helper.rb +++ b/app/helpers/multi_languages_helper.rb @@ -56,11 +56,12 @@ def search_languages # top ten spoken languages portal_languages.keys + %w[zh es hi ar bn pt ru ur id] end - def search_language_selector(id: 'search_language', name: 'search_language') + def search_language_selector(id: 'search_language', name: 'search_language', selected: nil) render Input::LanguageSelectorComponent.new(id: id, name: name, enable_all: true, languages: search_languages, 'data-select-input-searchable-value': false, - title: search_language_help_text) + title: search_language_help_text, + selected: selected&.to_sym) end diff --git a/app/helpers/ontologies_helper.rb b/app/helpers/ontologies_helper.rb index cb5ef894a..3a75d358a 100644 --- a/app/helpers/ontologies_helper.rb +++ b/app/helpers/ontologies_helper.rb @@ -149,7 +149,7 @@ def classes_link(ontology, count) def metadata_filled_count(submission = @submission_latest, ontology = @ontology) return if submission.nil? - + reject = [:csvDump, :dataDump, :openSearchDescription, :metrics, :prefLabelProperty, :definitionProperty, :definitionProperty, :synonymProperty, :authorProperty, :hierarchyProperty, :obsoleteProperty, :ontology, :endpoint, :submissionId, :submissionStatus, :uploadFilePath, :context, :links, :ontology] @@ -384,14 +384,23 @@ def lazy_load_section(section_title, &block) end def visits_chart_dataset(visits_data) - [{ - label: 'Visits', - data: visits_data, - backgroundColor: 'rgba(151, 187, 205, 0.2)', - borderColor: 'rgba(151, 187, 205, 1)', - pointBorderColor: 'rgba(151, 187, 205, 1)', - pointBackgroundColor: 'rgba(151, 187, 205, 1)', - }].to_json + visits_chart_dataset_array({'Visits': visits_data}) + end + + def visits_chart_dataset_array(visits_data, fill: true) + visits_data = visits_data.map do |label , x| + { + label: label, + data: x, + borderWidth: 2, + borderRadius: 5, + borderSkipped: false, + cubicInterpolationMode: 'monotone', + tension: 0.4, + fill: fill + } + end + visits_data.to_json end def submission_ready?(submission) @@ -406,7 +415,7 @@ def sections_to_show sections += %w[properties] sections += %w[schemes collections] if skos? sections += %w[instances] unless skos? - sections += %w[notes mappings widgets] + sections += %w[notes mappings widgets sparql] end sections end diff --git a/app/helpers/ontology_metrics_helper.rb b/app/helpers/ontology_metrics_helper.rb deleted file mode 100644 index aed124125..000000000 --- a/app/helpers/ontology_metrics_helper.rb +++ /dev/null @@ -1,46 +0,0 @@ -module OntologyMetricsHelper - - def format_metric_list(metrics, metric, title) - return 0 if metric.nil? - - markup = "" - - # IF all of the classes triggered the metric, return the class count - if metric.include?("alltriggered") - markup = "#{metrics.numberOfClasses}" - elsif metric.kind_of?(Array) && metric.length == 1 && metric[0].include?("limitpassed") - # Split at the magic marker and return the count - markup = metric[0].split(":")[1] - elsif metric.kind_of?(Hash) && metric.length == 1 && metric["limitpassed:"] - # Return the count, which is an int value to the key 'limitpassed:' - markup = metric["limitpassed:"] - elsif metric.kind_of?(Array) && metric.length == 0 - # If we have an empty array return 0 - markup = "0" - elsif metric.kind_of?(Array) - markup << "#{metric.length}" - markup << "" - elsif metric.kind_of?(Hash) - counts = [] - metric.each do |cls, count| - counts << "#{cls} (#{count})" - end - - metric = counts - - markup << "#{metric.length}" - markup << "" - else - markup = metric.to_s - end - - markup - end - -end diff --git a/app/helpers/properties_helper.rb b/app/helpers/properties_helper.rb new file mode 100644 index 000000000..a1ac0a241 --- /dev/null +++ b/app/helpers/properties_helper.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true +module PropertiesHelper + + def property_link(acronym, child, language) + child.id.eql?('bp_fake_root') ? '#' : "/ontologies/#{acronym}/properties/show?id=#{CGI.escape(child.id)}&language=#{language}" + end + + def property_children_link(acronym, child, language) + "/ajax/properties/children?propertyid=#{CGI.escape(child.id)}&language=#{language}&ontology=#{acronym}" + end + + def property_tree_data(acronym, child, language) + href = property_link(acronym, child, language) + children_link = property_children_link(acronym, child, language) + data = { + propertyid: child.id + } + [children_link, data, href] + end + def property_tree_component(root, selected_concept, acronym, language, sub_tree: false, id: nil, auto_click: false) + tree_component(root, selected_concept, target_frame: 'property_show', sub_tree: sub_tree, id: id, auto_click: auto_click) do |child| + property_tree_data(acronym, child, language) + end + end +end \ No newline at end of file diff --git a/app/helpers/reviews_helper.rb b/app/helpers/reviews_helper.rb deleted file mode 100644 index bb859ee87..000000000 --- a/app/helpers/reviews_helper.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ReviewsHelper - def organize_ratings(review) - return [ - { name: :usability, value: review.usabilityRating.to_i }, - { name: :coverage, value: review.coverageRating.to_i }, - { name: :quality, value: review.qualityRating.to_i }, - { name: :formality, value: review.formalityRating.to_i }, - { name: :correctness, value: review.correctnessRating.to_i }, - { name: :documentation, value: review.documentationRating.to_i } - ] - end -end diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index 3e024feec..daea3bc5b 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -78,31 +78,40 @@ def schemes_data [schemes_labels, main_scheme, selected_scheme] end - def tree_link_to_schemes(schemes_labels, main_scheme_label, selected_scheme_id) - out = '' + def schemes_tree(schemes_labels, main_scheme_label, selected_scheme_id) + selected_scheme = nil + schemes = sorted_labels(schemes_labels).map do |s| + next nil unless main_scheme_label.nil? || s['prefLabel'] != main_scheme_label['prefLabel'] + scheme = OpenStruct.new(s) + scheme.prefLabel = Array(get_scheme_label(s)).last + scheme.id = scheme['@id'] + selected_scheme = scheme if scheme.id.eql?(selected_scheme_id) + scheme + end.compact + + + main_scheme = nil + if main_scheme_label.nil? + children = schemes + else + main_scheme = OpenStruct.new(main_scheme_label) + main_scheme.prefLabel = Array(get_scheme_label(main_scheme_label)).last + main_scheme.children = schemes + main_scheme.id = main_scheme['@id'] + main_scheme['expanded?'] = true + main_scheme['hasChildren'] = true + children = [main_scheme] + end + root = OpenStruct.new + root.children = children + selected_scheme = selected_scheme || main_scheme || root.children.first - sorted_labels(schemes_labels).each do |s| - next unless main_scheme_label.nil? || s['prefLabel'] != main_scheme_label['prefLabel'] - out << <<-EOS -
        • - #{link_to_scheme(s, selected_scheme_id)} -
        • - EOS + tree_component(root, selected_scheme, target_frame: 'scheme', auto_click: true) do |child| + href = scheme_path(child['@id'], request_lang) rescue '' + data = { schemeid: (child['@id'] rescue '')} + ["#", data, href] end - out - end - def link_to_scheme(scheme, selected_scheme_id) - pref_label_lang, pref_label_html = get_scheme_label(scheme) - tooltip = pref_label_lang.to_s.eql?('@none') ? '' : "data-controller='tooltip' data-tooltip-position-value='right' title='#{pref_label_lang.upcase}'" - <<-EOS - - #{pref_label_html} - - EOS end end diff --git a/app/helpers/sparql_helper.rb b/app/helpers/sparql_helper.rb new file mode 100644 index 000000000..eb1a1eed0 --- /dev/null +++ b/app/helpers/sparql_helper.rb @@ -0,0 +1,67 @@ +module SparqlHelper + def change_from_clause(query, graph) + unless graph.blank? + graph = graph.gsub($REST_URL, 'http://data.bioontology.org') + + if query.match?(/FROM <[^>]+>/i) + # Use a regular expression to replace all instances of FROM + query = query.gsub(/FROM <[^>]+>/i, "") + else + query = query.gsub("WHERE", "FROM <#{graph}> WHERE") + end + + query = query.gsub(/GRAPH <[^>]+>/i, "") + end + query + end + def ontology_sparql_query(query, graph = '') + query = change_from_clause(query, graph) + sparql_query(query) + end + def is_allowed_query?(sparql_query) + forbidden_operations = [ + 'INSERT DATA', + 'DELETE DATA', + 'DELETE/INSERT', + 'DELETE', + 'INSERT', + 'DELETE WHERE', + 'LOAD', + 'CLEAR', + 'CREATE', + 'DROP', + 'COPY', + 'MOVE', + 'ADD' + ] + + # Define a regular expression to match SELECT queries + select_query_regex = /\A\s*SELECT\b/m + + # Check if the query contains any forbidden operations outside SELECT queries + return false if forbidden_operations.any? { |op| sparql_query.upcase.include?(op) && !sparql_query.match(select_query_regex) } + + true + end + + def sparql_query(query) + return 'No SPARQL endpoint configured' if $SPARQL_URL.blank? + return 'INSERT Queries not permitted' unless is_allowed_query?(query) + endpoint = $SPARQL_URL.gsub('test', 'sparql') + begin + conn = Faraday.new do |conn| + conn.options.timeout = 60 + end + response = conn.get("#{endpoint}?query=#{encode_param(query)}") + response.body.force_encoding('ISO-8859-1').encode('UTF-8') + rescue + "Query timeout" + end + end + def sparql_query_container(graph: nil) + content_tag(:div, '', data: {controller: 'sparql', + 'sparql-proxy-value': '/sparql_proxy/', + 'sparql-graph-value': graph}) + end + +end diff --git a/app/helpers/statistics_helper.rb b/app/helpers/statistics_helper.rb new file mode 100644 index 000000000..ee680c401 --- /dev/null +++ b/app/helpers/statistics_helper.rb @@ -0,0 +1,60 @@ +module StatisticsHelper + + def ontologies_by_year_month + data = LinkedData::Client::Analytics.all.to_h + data.delete(:links) + data.delete(:context) + year_month_count = {} + year_month_visits = {} + acronyms = [] + data.each do |acronym, ont| + ont.each do |year, months| + next if year.eql?(:links) || year.eql?(:context) + months.each do |month, count| + next if month.eql?(:links) || month.eql?(:context) + year_month_count[[year.to_s.to_i, month.to_s.to_i]] ||= [] + year_month_visits[[year.to_s.to_i, month.to_s.to_i]] = count + (year_month_visits[[year.to_s.to_i, month.to_s.to_i]] || 0) + + if !count.zero? && !acronyms.include?(acronym) + year_month_count[[year.to_s.to_i, month.to_s.to_i]] << acronym + acronyms << acronym + end + end + end + end + year_month_visits = year_month_visits.sort_by { |(year, month), _| [year, month] }.to_h + [year_month_count, year_month_visits] + end + + def string_year_month(year, month) + DateTime.parse("#{year}/#{month}").strftime("%b %Y") + end + def group_by_year_month(data) + data.group_by{|x| [Date.parse(x.created).year, Date.parse(x.created).month] }.sort_by { |(year, month), _| [year, month] }.to_h + end + + def merge_time_evolution_data(data) + min_year = data.map{|x| x.keys.first.first}.min + old = data.size.times.map { |x| 0 } + + visits_data = { visits: data.size.times.map { |x| [] }, labels: [] } + + (min_year..Date.today.year).each do |year| + (1..12).each do |month| + data.each_with_index do |x , i| + old[i] += x[[year, month]]&.size || 0 + end + + next if old.sum.zero? + + data.each_index do |i| + visits_data[:visits][i] << old[i] + end + + visits_data[:labels] << string_year_month(year, month) + end + end + visits_data + end + +end diff --git a/app/javascript/component_controllers/index.js b/app/javascript/component_controllers/index.js index 5eff9ec43..129e09a6f 100644 --- a/app/javascript/component_controllers/index.js +++ b/app/javascript/component_controllers/index.js @@ -28,5 +28,6 @@ application.register("search-input", Search_input_component_controller) application.register("tabs-container", Tabs_container_component_controller) application.register("circle-progress-bar", CircleProgressBarComponentController) application.register("alert-component", alert_component_controller) + application.register("progress-pages", Progress_pages_component_controller) application.register("reveal-component", Reveal_component_controller) diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js index 90a3788a2..f0624491d 100644 --- a/app/javascript/controllers/application.js +++ b/app/javascript/controllers/application.js @@ -17,6 +17,5 @@ application.register('read-more', ReadMore) import Timeago from 'stimulus-timeago' application.register('timeago', Timeago) export { application } -import Reveal from 'stimulus-reveal-controller' -application.register('reveal', Reveal) + diff --git a/app/javascript/controllers/class_search_auto_complete_controller.js b/app/javascript/controllers/class_search_auto_complete_controller.js index f9b0afe62..831cfee41 100644 --- a/app/javascript/controllers/class_search_auto_complete_controller.js +++ b/app/javascript/controllers/class_search_auto_complete_controller.js @@ -10,8 +10,6 @@ export default class extends OntoportalAutocompleteController { } onFindValue(li) { - jQuery.blockUI({ message: '

          Loading Class...

          ', showOverlay: false }); - if (li == null) { // User performs a search let search = confirm("Class could not be found.\n\nPress OK to go to the Search page or Cancel to continue browsing"); @@ -26,12 +24,7 @@ export default class extends OntoportalAutocompleteController { // Appropriate value selected if (li.extra) { let sValue = jQuery("#jump_to_concept_id").val() - document.location = "/ontologies/" + jQuery(document).data().bp.ontology.acronym + "/?p=classes&conceptid=" + encodeURIComponent(sValue) + "&jump_to_nav=true"; - jQuery.blockUI({ - message: '

          Loading Class...

          ', - showOverlay: false - }); - + Turbo.visit("/ontologies/" + jQuery(document).data().bp.ontology.acronym + "/?p=classes&conceptid=" + encodeURIComponent(sValue) + "&jump_to_nav=true") } } diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 8d4ea6942..e947f8f63 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -70,6 +70,9 @@ application.register("simple-tree", SimpleTreeController) import SkosCollectionColorsController from "./skos_collection_colors_controller" application.register("skos-collection-colors", SkosCollectionColorsController) +import SparqlController from "./sparql_controller" +application.register("sparql", SparqlController) + import TextTruncateController from "./text_truncate_controller" application.register("text-truncate", TextTruncateController) diff --git a/app/javascript/controllers/load_chart_controller.js b/app/javascript/controllers/load_chart_controller.js index 149d525b0..c55b5adbb 100644 --- a/app/javascript/controllers/load_chart_controller.js +++ b/app/javascript/controllers/load_chart_controller.js @@ -1,50 +1,51 @@ -import { Controller } from "@hotwired/stimulus" +import { Controller } from '@hotwired/stimulus' +import Chart from 'chart.js/auto' // Connects to data-controller="load-chart" export default class extends Controller { static values = { labels: Array, - datasets: Array + datasets: Array, + type: { type: String, default: 'line' }, + title: String, + indexAxis: { type: String, default: 'x' }, + legend: { type: Boolean, default: false } } - connect() { - const labels = this.labelsValue; - const datasets = this.datasetsValue; + connect () { - const context = this.element.getContext('2d'); + const labels = this.labelsValue + const datasets = this.datasetsValue + + const context = this.element.getContext('2d') this.chart = new Chart(context, { - type: 'line', + type: this.typeValue, data: { labels: labels, datasets: datasets }, options: { - responsive: true, - legend: { - display: false + indexAxis: this.indexAxisValue, + plugins: { + colors: {enabled: true}, + title: { + display: this.hasTitleValue, + text: this.titleValue + }, + legend: { + display: this.legendValue + } }, + responsive: true, scales: { - yAxes: [{ - ticks: { - beginAtZero: false, - callback: function (value, index, values) { - return numberWithCommas(value); - } - } - }] + x: this.#scales('x'), + y: this.#scales('y') }, - tooltips: { - displayColors: false, - callbacks: { - label: function (tooltipItem, data) { - return numberWithCommas(tooltipItem.yLabel); - } - } - } } - }); + }) + } disconnect () { @@ -52,4 +53,31 @@ export default class extends Controller { this.chart = null } + #scales (axe) { + if (this.indexAxisValue === axe) { + return { + border: { + display: false + }, + grid: { + display: false + }, + ticks: { + beginAtZero: false + } + } + } else { + return { + border: { + display: false + }, + grid: { + display: false + }, + ticks: { + display: false + } + } + } + } } diff --git a/app/javascript/controllers/simple_tree_controller.js b/app/javascript/controllers/simple_tree_controller.js index bcc04cc7a..2333fe906 100644 --- a/app/javascript/controllers/simple_tree_controller.js +++ b/app/javascript/controllers/simple_tree_controller.js @@ -1,76 +1,64 @@ -import {Controller} from "@hotwired/stimulus" -import {useSimpleTree} from "../mixins/useSimpleTree"; - +import { Controller } from '@hotwired/stimulus' // Connects to data-controller="simple-tree" export default class extends Controller { - static values = { - autoClick: {type: Boolean, default: false} - } - - connect() { - this.simpleTreeCollection = useSimpleTree(this.element, - this.#afterClick.bind(this), - this.#afterAjaxError.bind(this), - this.#beforeAjax.bind(this) - ) - - - this.simpleTreeCollection.ready(() => { - let activeElem = this.element.querySelector('a.active') - if (activeElem) { - $(this.element).scrollTo($(activeElem)) - - if (this.autoClickValue) { - activeElem.click() - } - } + static values = { + autoClick: { type: Boolean, default: false } + } + connect () { + let activeElem = this.element.querySelector('a.active') + if (activeElem) { + this.element.scrollTo({ + top: activeElem.offsetTop, + behavior: 'smooth' + }); - }) - - this.#onClickTooManyChildrenInit() - } - - #onClickTooManyChildrenInit() { - jQuery(".too_many_children_override").live('click', (event) => { - event.preventDefault(); - let result = jQuery(event.target).closest("ul"); - result.html(""); - jQuery.ajax({ - url: jQuery(event.target).attr('href'), - context: result, - success: function (data) { - this.html(data); - this.simpleTreeCollection.get(0).setTreeNodes(this); - }, - error: function () { - this.html("
          Problem getting children. Try again
          "); - } - }); - }); + if (this.autoClickValue) { + activeElem.click() + } } + this.#onClickTooManyChildrenInit() + } - #afterClick(node) { - this.element.dispatchEvent(new CustomEvent('clicked', { - detail: { - node: node, - data: {...node.context.dataset} + select (event) { + this.element.querySelector('a.active')?.classList.toggle('active') + event.currentTarget.classList.toggle('active') + this.#afterClick(event.currentTarget) + } - } - })) - } + toggleChildren (event) { + event.preventDefault() + event.target.classList.toggle('fa-chevron-right') + event.target.classList.toggle('fa-chevron-down') + event.target.nextElementSibling.nextElementSibling.classList.toggle('hidden') + } - #afterAjaxError(node) { - this.simpleTreeCollection[0].option.animate = false; - this.simpleTreeCollection.get(0).nodeToggle(node.parent()[0]); - if (node.parent().children(".expansion_error").length === 0) { - node.parent().append("Error, please try again"); + #onClickTooManyChildrenInit () { + jQuery('.too_many_children_override').live('click', (event) => { + event.preventDefault() + let result = jQuery(event.target).closest('ul') + result.html('') + jQuery.ajax({ + url: jQuery(event.target).attr('href'), + context: result, + success: function (data) { + this.html(data) + this.simpleTreeCollection.get(0).setTreeNodes(this) + }, + error: function () { + this.html('
          Problem getting children. Try again
          ') } - this.simpleTreeCollection[0].option.animate = true; - } + }) + }) + } - #beforeAjax(node) { - node.parent().children(".expansion_error").remove(); - } + #afterClick (node) { + this.element.dispatchEvent(new CustomEvent('clicked', { + detail: { + node: node, + data: { ...node.dataset } + }, bubbles: true + })) + } } diff --git a/app/javascript/controllers/sparql_controller.js b/app/javascript/controllers/sparql_controller.js new file mode 100644 index 000000000..31227f4fd --- /dev/null +++ b/app/javascript/controllers/sparql_controller.js @@ -0,0 +1,25 @@ +import { Controller } from '@hotwired/stimulus' +import { getYasgui } from '../mixins/useYasgui' + +// Connects to data-controller="sparql" +export default class extends Controller { + static values = { + proxy: String, + graph: String, + } + connect () { + localStorage.removeItem('yagui__config'); + this.yasgui = getYasgui(this.element, + { + corsProxy: this.proxyValue, + copyEndpointOnNewTab: true, + requestConfig: { + endpoint: this.proxyValue, + acceptHeaderGraph: false, + acceptHeaderUpdate: false, + namedGraphs: [this.graphValue], + } + }) + + } +} diff --git a/app/javascript/mixins/useHistory.js b/app/javascript/mixins/useHistory.js index f84ff4133..ce895f1a6 100644 --- a/app/javascript/mixins/useHistory.js +++ b/app/javascript/mixins/useHistory.js @@ -1,18 +1,20 @@ export class HistoryService { - unWantedData = ['turbo', 'controller', 'target', 'value'] + unWantedData = ['turbo', 'controller', 'target', 'value', 'action'] constructor() { - this.history = History + this.history = window.history + this.state = {data: {}} } pushState(data, title, url) { this.history.pushState(data, title, url) + this.state = {data: data} } getState() { - return this.history.getState() + return this.state } updateHistory(currentUrl, newData) { diff --git a/app/javascript/mixins/useSimpleTree.js b/app/javascript/mixins/useSimpleTree.js deleted file mode 100644 index ef61bea2d..000000000 --- a/app/javascript/mixins/useSimpleTree.js +++ /dev/null @@ -1,11 +0,0 @@ -export function useSimpleTree(element, afterClick, afterAjaxError, beforeAjax ) { - return jQuery(element).simpleTree({ - autoclose: false, - drag: false, - animate: true, - timeout: 20000, - afterClick: afterClick, - afterAjaxError: afterAjaxError, - beforeAjax: beforeAjax - }); -} \ No newline at end of file diff --git a/app/javascript/mixins/useYasgui.js b/app/javascript/mixins/useYasgui.js new file mode 100644 index 000000000..e13188157 --- /dev/null +++ b/app/javascript/mixins/useYasgui.js @@ -0,0 +1,6 @@ +import Yasgui from "@triply/yasgui"; + +export const getYasgui = (elem, config) => { + return new Yasgui(elem, config) +} + diff --git a/app/models/history.rb b/app/models/history.rb deleted file mode 100644 index 56aac8792..000000000 --- a/app/models/history.rb +++ /dev/null @@ -1,10 +0,0 @@ -class History - attr_accessor :ontology_id, :ontology_name, :ontology_acronym, :concept - - def initialize(ontology_id, ontology_name, ontology_acronym, concept) - self.ontology_id = ontology_id - self.ontology_name = ontology_name - self.ontology_acronym = ontology_acronym - self.concept = concept - end -end \ No newline at end of file diff --git a/app/views/admin/_analytics.html.haml b/app/views/admin/_analytics.html.haml new file mode 100644 index 000000000..e70fbf0f5 --- /dev/null +++ b/app/views/admin/_analytics.html.haml @@ -0,0 +1,47 @@ +%div.container + %div.analytics.my-5 + %dive.site-admin-page-section.flex-wrap.justify-content-center + %div.d-flex.flex-wrap.m-2.justify-content-center.w-100 + %div.col.card.d-flex.justify-content-center.mr-2.p-2.text-center.container-gradient + %div.card-header-2 + Total Ontologies + %div.card-header-1 + = @ontologies_count + %div.card-header-3{data: {controller: 'tooltip'}, title: new_ontologies_created_title} + #{@new_ontologies_count.size} new ontology this year + %div.col.card.d-flex.justify-content-center.p-2.mr-2.text-center.container-gradient + %div.card-header-2 + Ontologies with errors + %div.card-header-1 + = @ontologies_problems_count + %div.card-header-3.py-2.mt-1 + %div.col.card.d-flex.justify-content-center.mr-2.p-2.text-center.container-gradient + %div.card-header-2 + Total Visits + %div.card-header-1 + = format_number_abbreviated(@page_visits[:visits].sum) + %div.card-header-3.py-2.mt-1 + + %div.col.card.d-flex.justify-content-center.p-2.text-center.container-gradient + %div.card-header-2 + Active Users + %div.card-header-1 + = @users_visits[:visits].last + %div.card-header-3 + #{ visits_evolution } users since last month + %div.d-flex.flex-wrap.m-2.p-2.justify-content-center.w-100 + %div.col.card.p-2.mr-2 + = chart_component(title: 'Ontology visits', type: 'bar', + labels: @ontology_visits[:labels].last(13), + datasets: visits_chart_dataset(@ontology_visits[:visits].last(13))) + + %div.col.card.p-2 + = chart_component(title: 'Unique Users visits', type: 'line', + labels: @users_visits[:labels].last(13), + datasets: visits_chart_dataset(@users_visits[:visits].last(13))) + + %div.col-12.card.p-2.m-2 + = chart_component(title: 'Page visits of this month', type: 'bar', + labels: @page_visits[:labels].last(13).reverse, + datasets: visits_chart_dataset(@page_visits[:visits].last(13).reverse), + index_axis: 'y') \ No newline at end of file diff --git a/app/views/admin/_main.html.haml b/app/views/admin/_main.html.haml new file mode 100644 index 000000000..61b1ff51c --- /dev/null +++ b/app/views/admin/_main.html.haml @@ -0,0 +1,16 @@ +%div.container + %div#site-admin-clear-caches.my-5 + %div.site-admin-page-header + CACHE MANAGEMENT + %dive.site-admin-page-section + = action_button("Flush UI cache", admin_clearcache_path, class_style: 'btn btn-primary mx-1') + = action_button("Reset UI cache connection", admin_resetcache_path, class_style: 'btn btn-primary mx-1') + = action_button("Flush GOO cache", admin_clear_goo_cache_path, class_style: 'btn btn-primary mx-1') + = action_button("Flush HTTP cache", admin_clear_http_cache_path, class_style: 'btn btn-primary mx-1') + + %div.mb-5 + %div.site-admin-page-header + VERSION MANAGEMENT + %dive.site-admin-page-section + = render TurboFrameComponent.new(id: 'update_check_frame', src: '/admin/update_check_enabled') do |c| + - c.loader { 'Checking for updates ...' } diff --git a/app/views/admin/categories/_category.html.haml b/app/views/admin/categories/_category.html.haml new file mode 100644 index 000000000..c3521385b --- /dev/null +++ b/app/views/admin/categories/_category.html.haml @@ -0,0 +1,25 @@ +%tr.human{:id => category.id.split('/').last} + - count = category.ontologies&.size || 0 + %td + %div{style: 'width: 250px'} + %div.text-truncate{title: category.name} + = category.name + %td + = category.description + %td + = category.created + %td + = category.acronym + %td + = link_to count , "/ontologies?categories=#{category.id}", target: "_blank" + %td + %div.d-flex.align-items-center{style: 'width: 250px'} + %span.mx-1 + = link_to_modal(nil, edit_admin_category_path(category.id.split('/').last), data: {show_modal_title_value: category.name}) do + Edit + %span + - if count.zero? + = button_to "Delete", CGI.unescape(admin_category_path(category.id.split('/').last)), method: :delete, class: 'btn btn-link', form: {data: { turbo: true, turbo_confirm: "Are you sure?", turbo_frame: '_top'}} + - else + %span{data: { controller: 'tooltip' }, title: "Can't delete this category because still used"} + = link_to "Delete", "", class: 'btn btn-link disabled' diff --git a/app/views/admin/categories/_form.html.haml b/app/views/admin/categories/_form.html.haml index 85007d45f..7cd675a0b 100644 --- a/app/views/admin/categories/_form.html.haml +++ b/app/views/admin/categories/_form.html.haml @@ -1,77 +1,46 @@ -- new_record = @category.acronym.nil? += render_in_modal do + - new_record = @category.acronym.nil? -:javascript - function validateCategoryForm() { - var acronymField = document.getElementById('category_acronym'); - var nameField = document.getElementById('category_name'); - var errorAcronym = document.getElementById('error-category-acronym'); - var errorName = document.getElementById('error-category-name'); - - errorAcronym.innerText = ''; - errorName.innerText = ''; - - var isValid = true; - - if (acronymField.value.trim() === '') { - errorAcronym.innerText = 'Please enter an acronym'; - isValid = false; - } - - if (nameField.value.trim() === '') { - errorName.innerText = 'Please enter a name'; - isValid = false; - } - - return isValid; - } - -%div.alert-box.error{style: @errors.nil? ? 'display: none' : nil } - %ul - - unless @errors.nil? - - for error in @errors - %li - = error -%div{:style => "width:500px"} - %span.asterik{:style => "float:right;"} * fields are required - %h3 #{title_text} - %table#category_form.form - %colgroup - %col - %col{style: "width: 100%"} - %tr - %th - Acronym - %span.asterik * - %td.top - - if new_record - = f.text_field :acronym, class: "form-control", id: "category_acronym" - %div#error-category-acronym.groupFormError - - else - = f.text_field :acronym, class: "form-control", readonly: true - %tr - %th - Name - %span.asterik * - %td.top - = f.text_field :name, class: "form-control", id: "category_name" - %div#error-category-name.groupFormError - %tr - %th - Description - %td.top - = f.text_area :description, class: "form-control" - - unless new_record + %div + = render_alerts_container('category') + %div + %table#category_form.form + %colgroup + %col + %col{style: "width: 100%"} + %tr + %th + Acronym + %span.asterik * + %td.top + - if new_record + = f.text_field :acronym, class: "form-control", id: "category_acronym", required: true + %div#error-category-acronym.groupFormError + - else + = f.text_field :acronym, class: "form-control", readonly: true %tr %th - Created + Name + %span.asterik * %td.top - = f.text_field :created, readonly: true, class: "form-control" + = f.text_field :name, class: "form-control", id: "category_name", required: true + %div#error-category-name.groupFormError %tr %th - Ontologies + Description %td.top - = render SelectInputComponent.new(id: "category_ontologies", name: "category[ontologies]", values: @ontologies_category, selected: @category.ontologies, multiple: true, open_to_add_values: true) - %div.d-flex.mt-1{:style => "display: none;"} - %div.mt-2 - %input{type: "submit", value: button_text, class: "btn btn-primary mr-sm-2 group-form-accept", onclick: "return validateCategoryForm();"} - %a{href: "javascript:;", class: "btn btn-primary dismiss-dialog"} Cancel \ No newline at end of file + = f.text_area :description, class: "form-control" + - unless new_record + %tr + %th + Created + %td.top + = f.text_field :created, readonly: true, class: "form-control" + %tr + %th + Ontologies + %td.top + = render SelectInputComponent.new(id: "category_ontologies", name: "category[ontologies]", values: @ontologies_category, selected: @category.ontologies, multiple: true, open_to_add_values: true) + %div.d-flex.mt-1{:style => "display: none;"} + %div.mt-2 + %input{type: "submit", value: button_text, class: "btn btn-primary mr-sm-2 group-form-accept", onclick: "return validateCategoryForm();"} \ No newline at end of file diff --git a/app/views/admin/categories/edit.html.haml b/app/views/admin/categories/edit.html.haml index b203c02ce..95feedf25 100644 --- a/app/views/admin/categories/edit.html.haml +++ b/app/views/admin/categories/edit.html.haml @@ -1,6 +1,7 @@ -- @title = "Edit category" += render_in_modal do + - @title = "Edit category" -%div - = form_for :category, url: admin_categories_path + "/" + escape(@category.acronym), method: "PATCH", remote: true, data: { collection: "categories"}, html: {class: "admin-collection-form" } do |f| - = render partial: "form", locals: {f: f, title_text: "Edit category", - button_text: "Save", button_class: "edit-category"} + %div + = form_for :category, url: admin_categories_path + "/" + escape(@category.acronym), method: "PATCH", remote: true, data: { collection: "category"}, html: {class: "admin-collection-form" } do |f| + = render partial: "form", locals: {f: f, title_text: "Edit category", + button_text: "Save", button_class: "edit-category"} diff --git a/app/views/admin/categories/index.html.haml b/app/views/admin/categories/index.html.haml new file mode 100644 index 000000000..ed3e642de --- /dev/null +++ b/app/views/admin/categories/index.html.haml @@ -0,0 +1,24 @@ += turbo_frame_tag 'categories-list' do + = link_to_modal("Create New category", + new_admin_category_path, + id: "new_category_btn", + class: "btn btn-default mb-3", + data: { show_modal_title_value: "Create a new category"}) + + %div.my-1.border-radius-0 + = render_alerts_container + = render TableComponent.new(id: 'admin_categories', custom_class: 'border rounded p-1') do |t| + - t.header do |h| + - h.th {'Name'} + - h.th {'Description'} + - h.th {'Created'} + - h.th {'ID'} + - h.th {'COUNT'} + - h.th {'Actions'} + = render partial: 'admin/categories/category', collection: @categories + %tr.empty-state + %td.text-center{:colspan => "6"} There are currently no categories. + :javascript + $.fn.dataTable.ext.errMode = 'none'; + $("#admin_categories").dataTable() + diff --git a/app/views/admin/categories/new.html.haml b/app/views/admin/categories/new.html.haml index 4d3b3082d..b4f95b777 100644 --- a/app/views/admin/categories/new.html.haml +++ b/app/views/admin/categories/new.html.haml @@ -1,6 +1,7 @@ -- @title = "Create new category" += render_in_modal do + - @title = "Create new category" -%div - = form_for :category, url: admin_categories_path, method: "POST", remote: true, data: { collection: "categories"}, html: {class: "admin-collection-form" } do |f| - = render partial: "form", locals: {f: f, title_text: "Create category", - button_text: "Create category" } + %div + = form_for :category, url: admin_categories_path, method: "POST", remote: true, data: { collection: "categories"}, html: {class: "admin-collection-form" } do |f| + = render partial: "form", locals: {f: f, title_text: "Create category", + button_text: "Create category" } diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 87c452044..c43278b54 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -1,38 +1,8 @@ - new_record = @group.acronym.nil? -:javascript - function validateForm() { - var acronymField = document.getElementById('group_acronym'); - var nameField = document.getElementById('group_name'); - var errorAcronym = document.getElementById('error-acronym'); - var errorName = document.getElementById('error-name'); - - errorAcronym.innerText = ''; - errorName.innerText = ''; - - var isValid = true; - - if (acronymField.value.trim() === '') { - errorAcronym.innerText = 'Please enter an acronym'; - isValid = false; - } - - if (nameField.value.trim() === '') { - errorName.innerText = 'Please enter a name'; - isValid = false; - } - - return isValid; - } -%div.alert-box.error{style: @errors.nil? ? 'display: none' : nil } - %ul - - unless @errors.nil? - - for error in @errors - %li - = error -%div{:style => "width:500px"} - %span.asterik{:style => "float:right;"} * fields are required - %h3 #{title_text} +%div + = render_alerts_container('group') +%div %table#group_form.form %colgroup %col @@ -43,7 +13,7 @@ %span.asterik * %td.top - if new_record - = f.text_field :acronym, class: "form-control", id: "group_acronym" + = f.text_field :acronym, class: "form-control", id: "group_acronym", required: true %div#error-acronym.groupFormError - else = f.text_field :acronym, class: "form-control", readonly: true @@ -52,7 +22,7 @@ Name %span.asterik * %td.top - = f.text_field :name, class: "form-control", id: "group_name" + = f.text_field :name, class: "form-control", id: "group_name", required: true %div#error-name.groupFormError %tr %th @@ -71,5 +41,4 @@ %td.top = render SelectInputComponent.new(id: "group_ontologies", name: "group[ontologies]", values: @ontologies_group , selected: @group.ontologies , multiple: true, open_to_add_values: true) %div.mt-2 - %input{type: "submit", value: button_text, class: "btn btn-primary mr-sm-2 group-form-accept", onclick: "return validateForm();"} - %a{href: "javascript:;", class: "btn btn-primary dismiss-dialog"} Cancel + %input{type: "submit", value: button_text, class: "btn btn-primary mr-sm-2 group-form-accept"} diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml new file mode 100644 index 000000000..9200a6a84 --- /dev/null +++ b/app/views/admin/groups/_group.html.haml @@ -0,0 +1,25 @@ +%tr.human{:id => group.id.split('/').last} + - count = group.ontologies&.size || 0 + %td + %div{style: 'width: 250px'} + %div.text-truncate{title: group.name} + = group.name + %td + = group.description + %td + = group.created + %td + = group.acronym + %td + = link_to count , "/ontologies?groups=#{group.id}", target: "_blank" + %td{:class => 'delete_mappings_column'} + %div.d-flex.align-items-center{style: 'width: 250px'} + %span.mx-1 + = link_to_modal(nil, edit_admin_group_path(group.id.split('/').last), data: {show_modal_title_value: group.name}) do + Edit + %span + - if count.zero? + = button_to "Delete", CGI.unescape(admin_group_path(group.id.split('/').last)), method: :delete, class: 'btn btn-link', form: {data: { turbo: true, turbo_confirm: "Are you sure?", turbo_frame: '_top'}} + - else + %span{data: { controller: 'tooltip' }, title: "Can't delete this group because still used"} + = link_to "Delete", "", class: 'btn btn-link disabled' diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml index 7269a374d..c5275f429 100644 --- a/app/views/admin/groups/edit.html.haml +++ b/app/views/admin/groups/edit.html.haml @@ -1,6 +1,7 @@ -- @title = "Edit group" += render_in_modal do + - @title = "Edit group" -%div - = form_for :group, url: admin_groups_path + "/" + escape(@group.acronym), method: "PATCH", remote: true, data: { collection: "groups"}, html: {class: "admin-collection-form" } do |f| - = render partial: "form", locals: {f: f, title_text: "Edit group", - button_text: "Save", button_class: "edit-group"} + %div + = form_for :group, url: admin_groups_path + "/" + escape(@group.acronym), method: "PATCH", remote: true, data: { collection: "groups"}, html: {class: "admin-collection-form" } do |f| + = render partial: "form", locals: {f: f, title_text: "Edit group", + button_text: "Save", button_class: "edit-group"} diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml new file mode 100644 index 000000000..4328ae7e5 --- /dev/null +++ b/app/views/admin/groups/index.html.haml @@ -0,0 +1,28 @@ += turbo_frame_tag 'groups-list' do + %div.d-flex + = link_to_modal("Create New group", + new_admin_group_path, + id: "new_group_btn", + class: "btn btn-default mb-3", + data: { show_modal_title_value: "Create a new group"}) + + = action_button("Synchronize groups with slices", admin_groups_synchronize_groups_url) + + %div.my-1 + = render_alerts_container + = render TableComponent.new(id: 'admin_groups', custom_class: 'border rounded p-1') do |t| + - t.header do |h| + - h.th {'Name'} + - h.th {'Description'} + - h.th {'Created'} + - h.th {'ID'} + - h.th {'COUNT'} + - h.th {'Actions'} + + = render partial: 'admin/groups/group', collection: @groups + %tr.empty-state + %td.text-center{:colspan => "6"} There are currently no agents. + :javascript + $.fn.dataTable.ext.errMode = 'none'; + $("#admin_groups").dataTable() + diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml index 2a8bf5f42..48eeb55ed 100644 --- a/app/views/admin/groups/new.html.haml +++ b/app/views/admin/groups/new.html.haml @@ -1,6 +1,7 @@ -- @title = "Create new group" += render_in_modal do + - @title = "Create new group" -%div - = form_for :group, url: admin_groups_path, method: "POST", remote: true, data: { collection: "groups"}, html: {class: "admin-collection-form" } do |f| - = render partial: "form", locals: {f: f, title_text: "Create group", - button_text: "Create group" } + %div + = form_for :group, url: admin_groups_path, method: "POST", remote: true, data: { collection: "groups"}, html: {class: "admin-collection-form" } do |f| + = render partial: "form", locals: {f: f, title_text: "Create group", + button_text: "Create group" } diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index b92443631..7a3d863d0 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -1,54 +1,30 @@ - @title = "Administration" -%div.row - %div.col - %h3.my-4 Administration Console +%div + %div.container + %h3.my-1.py-5 Administration Console +%div + %div.container.my-3 + = render_alerts_container + = render Display::AlertComponent.new(id: 'progress_message', type:'info') + = render Display::AlertComponent.new(id: 'success_message', type:'success') + = render Display::AlertComponent.new(id: 'error_message', type:'danger') + = render Display::AlertComponent.new(id: 'info_message', type:'info') -%div.row - %div.col-2 - %div#progress_message{:class => "alert-box progress_box"} - %div#success_message{:class => "alert-box success"} - %div#error_message{:class => "alert-box error"} - %div#info_message{:class => "alert-box notice"} - -%div.row - %div.col - - sections = ['Site Administration','Ontology Administration', 'Licensing', 'Users', 'Metadata Administration', 'Groups', 'Categories', 'Persons & Organizations'] - = render TabsContainerComponent.new do |t| +%div + %div.mx-1 + - sections = ['Analytics', 'Site Administration','Ontology Administration', 'Licensing', 'Users', 'Metadata Administration', 'Groups', 'Categories', 'Persons & Organizations', 'SPARQL'] + - selected = params[:section] || sections.first.downcase + = render TabsContainerComponent.new(url_parameter: 'section') do |t| - sections.each do |section_title| - t.item(title: section_title, path: '', - selected: section_title.eql?(sections.first), + selected: section_title.downcase.eql?(selected.downcase), page_name: '') - t.item_content do - %div#site-admin-clear-caches.my-5 - %div.site-admin-page-header - CACHE MANAGEMENT - %dive.site-admin-page-section - = link_to("Flush UI cache", "#", id: "flush_memcache_action", class: "btn btn-outline-secondary btn-sm admin-action-item", role: "button") - = link_to("Reset UI cache connection", "#", id: "reset_memcache_connection_action", class: "btn btn-outline-secondary btn-sm admin-action-item", role: "button") - = link_to("Flush GOO cache", "#", id: "flush_goo_cache_action", class: "btn btn-outline-secondary btn-sm admin-action-item", role: "button") - = link_to("Flush HTTP cache", "#", id: "flush_http_cache_action", class: "btn btn-outline-secondary btn-sm", role: "button") - - %div#site-admin-update-check.mb-5 - %div.site-admin-page-header - VERSION MANAGEMENT - %dive.site-admin-page-section - = link_to("Check for updates", "#", id: "update_check_action", class: "btn btn-outline-secondary btn-sm", role: "button") - - %div#site-admin-appliance-id.mb-5 - %div.site-admin-page-header - APPLIANCE ID - %dive.site-admin-page-section - %div#appliance-id - %span - - %div#site-admin-clear-caches.my-5 - %div.site-admin-page-header - MONITORING LINKS - %dive.site-admin-page-section - = link_to("Newrelic", "https://login.newrelic.com/login", target: '_blank', id: "newrelic_link", class: "btn btn-outline-secondary btn-sm admin-action-item", role: "button") - = link_to("StatusCake", "https://app.statuscake.com/Login", target: '_blank', id: "statuscake_link", class: "btn btn-outline-secondary btn-sm admin-action-item", role: "button") + = render 'analytics' + - t.item_content do + = render 'main' - t.item_content do %div.ontologies_list_container.mt-3 %table{:style => "float:left;"} @@ -79,21 +55,22 @@ %div.mb-5#renew-license-form - t.item_content do %div.ontologies_list_container.mt-3 - %table#adminUsers.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + %div.mx-auto.w-75 + = render TurboFrameComponent.new(id: 'users-list', src: users_path, loading: 'lazy') - t.item_content do = render partial: 'ontologies_metadata_curator/metadata_tab' - t.item_content do %div.ontologies_list_container.mt-3 - %table#adminGroups.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + %div.mx-auto.w-75 + = render TurboFrameComponent.new(id: 'groups-list', src: admin_groups_path, loading: 'lazy') - t.item_content do %div.ontologies_list_container.mt-3 - %table#adminCategories.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + %div.mx-auto.w-75 + = render TurboFrameComponent.new(id: 'categories-list', src: admin_categories_path, loading: 'lazy') - t.item_content do %div.ontologies_list_container.mt-3 %div.mx-auto.w-75 - = render TurboFrameComponent.new(id: 'agents-list', src: '/agents', loading: 'lazy') - - -# Groups tab - %div.tab-pane.fade{id: "groups", role: "tabpanel", aria: { labelledby: "groups-admin-tab" }} - %div.ontologies_list_container.mt-3 - %table#adminGroups.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} + = render TurboFrameComponent.new(id: 'agents-list', src: agents_path, loading: 'lazy') + - t.item_content do + %div.container + = sparql_query_container diff --git a/app/views/agents/_show_line.html.haml b/app/views/agents/_agent.html.haml similarity index 73% rename from app/views/agents/_show_line.html.haml rename to app/views/agents/_agent.html.haml index 840539d6c..c346ba3a3 100644 --- a/app/views/agents/_show_line.html.haml +++ b/app/views/agents/_agent.html.haml @@ -9,13 +9,14 @@ = raw agent.affiliations.map{|i| display_agent(i)}.join(', ') %td = agent.agentType + %td + - count = agent_usages_count(agent) + = link_to_modal(nil, "/agents/#{agent.id.split('/').last}/usages", style: 'width: 120px', class: "btn btn-sm btn-#{count.zero? ? 'danger' : 'light'}", data: { show_modal_title_value: "Agent \"#{agent.name}\" usages" }) do + = count.zero? ? "Not used" : "See usages (#{count})" + %td{:class => 'delete_mappings_column'} - if agent.id && !agent.id.empty? && session[:user] && session[:user].admin? %div.d-flex{style: 'width: 250px'} - %span.mx-1 - - count = agent_usages_count(agent) - = link_to_modal(nil, "/agents/#{agent.id.split('/').last}/usages", style: 'width: 120px', class: "btn btn-sm btn-#{count.zero? ? 'danger' : 'light'}", data: { show_modal_title_value: "Agent \"#{agent.name}\" usages" }) do - = count.zero? ? "Not used" : "See usages (#{count})" %span.mx-1 = link_to_agent_edit_modal(agent) %span diff --git a/app/views/agents/_table_list.html.haml b/app/views/agents/_table_list.html.haml deleted file mode 100644 index 6fe4555cd..000000000 --- a/app/views/agents/_table_list.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -= render_alerts_container(AgentsController) -%table#adminAgents.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"} - %thead - %tr - %th Name - %th Identifiers - %th Affiliations - %th Type - %th{:class => 'delete_mappings_column'} Actions - %tbody#agents_table_content - - agents.each do |agent| - = render partial: 'agents/show_line' , locals: {agent: agent} - %tr.empty-state - %td.text-center{:colspan => "6"} There are currently no agents. -:javascript - $.fn.dataTable.ext.errMode = 'none'; - - $("#adminAgents").dataTable() \ No newline at end of file diff --git a/app/views/agents/index.html.haml b/app/views/agents/index.html.haml index 83edbd239..6003089a7 100644 --- a/app/views/agents/index.html.haml +++ b/app/views/agents/index.html.haml @@ -6,4 +6,19 @@ class: "btn btn-default mb-3", data: { show_modal_title_value: "Create a new agent", show_modal_size_value: 'modal-xl' }, ) - = render partial: 'table_list', locals: {agents: @agents} \ No newline at end of file + = render_alerts_container(AgentsController) + = render TableComponent.new(id: 'admin_agents', custom_class: 'border rounded p-1') do |t| + - t.header do |h| + - h.th {'First name'} + - h.th {'Identifiers'} + - h.th {'Affiliations'} + - h.th {'Type'} + - h.th {'Usages'} + - h.th {'Actions'} + + = render partial: 'agents/agent' , collection: @agents + %tr.empty-state + %td.text-center{:colspan => "6"} There are currently no agents. + :javascript + $.fn.dataTable.ext.errMode = 'none'; + $("#admin_agents").dataTable() \ No newline at end of file diff --git a/app/views/application/_ga_tracking.html.haml b/app/views/application/_ga_tracking.html.haml index 73308e4cd..405f77c2b 100644 --- a/app/views/application/_ga_tracking.html.haml +++ b/app/views/application/_ga_tracking.html.haml @@ -1,9 +1,10 @@ --# Google Analytics -- unless $ANALYTICS_ID.nil? || $ANALYTICS_ID.empty? +-# Google tag +- tag_id = Rails.application.credentials.dig(:google_analytics, :tag_id) || $ANALYTICS_ID +- if tag_id.present? + %script - window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date; - ga('create', '#{$ANALYTICS_ID}', 'auto'); - ga('send', 'pageview'); - + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); - \ No newline at end of file + gtag('config', '#{tag_id}'); \ No newline at end of file diff --git a/app/views/collections/_list_view.html.haml b/app/views/collections/_list_view.html.haml index bfe256748..42ea1e67a 100644 --- a/app/views/collections/_list_view.html.haml +++ b/app/views/collections/_list_view.html.haml @@ -1,13 +1,15 @@ -- collections_labels, selected_collection = get_collections_labels(@collections, params[:collection_id]) +- collections_labels, selected_collection = get_collections_labels(@collections, params[:collectionid]) - selected_collection_id = selected_collection.nil? ? collections_labels.first["@id"] : selected_collection["@id"] - if collections_labels.empty? %div no collections detected - else %div - %ul.simpleTree{data:{controller: 'simple-tree', 'simple-tree': { 'auto-click-value': "true" }, action: 'clicked->history#updateURL'}} - %li.root - %ul - - sort_collections_label(collections_labels).each do |c| - %li.doc - = raw link_to_collection(c, selected_collection_id) \ No newline at end of file + = render TreeViewComponent.new(id: nil, auto_click: true) do |tree_child| + - sort_collections_label(collections_labels).each do |collection| + - scheme = OpenStruct.new(collection) + - scheme.prefLabel = Array(get_collection_label(collection)).last + - scheme.id = scheme['@id'] + - tree_child.child(child: scheme, href: collection_path(collection['@id'], request_lang), + children_href: '#', selected: scheme.id.eql?(selected_collection_id), + target_frame: 'collection', data: {collectionid: collection['@id']}) diff --git a/app/views/concepts/_biomixer.html.erb b/app/views/concepts/_biomixer.html.erb deleted file mode 100644 index 512ab2482..000000000 --- a/app/views/concepts/_biomixer.html.erb +++ /dev/null @@ -1,69 +0,0 @@ -<%require 'cgi'%> -<% user_api_key = (session[:user].try(:apikey) || $BIOMIXER_APIKEY).to_s %> -<% rest_domain = $REST_URL.sub(/https?:\/\//, "") %> -<% src_url = "#{$BIOMIXER_URL}/?mode=embed&embed_mode=paths_to_root&ontology_acronym=#{@ontology.acronym}&full_concept_id=#{CGI.escape(@concept.fullId)}&userapikey=#{user_api_key}&restURLPrefix=#{rest_domain}" %> -<% original_src = src_url%> - - - -
          - -
          diff --git a/app/views/concepts/_biomixer.html.haml b/app/views/concepts/_biomixer.html.haml new file mode 100644 index 000000000..9c6fe1332 --- /dev/null +++ b/app/views/concepts/_biomixer.html.haml @@ -0,0 +1,47 @@ += render_in_modal do + - require 'cgi' + - user_api_key = (session[:user].try(:apikey) || $BIOMIXER_APIKEY).to_s + - rest_domain = $REST_URL.sub(/https?:\/\//, "") + - src_url = "#{$BIOMIXER_URL}/?mode=embed&embed_mode=paths_to_root&ontology_acronym=#{CGI.escape(@ontology.acronym)}&full_concept_id=#{CGI.escape(@concept.fullId)}&userapikey=#{user_api_key}&restURLPrefix=#{rest_domain}" + - original_src = src_url + + :javascript + jQuery(document).data().bp.biomixer_fullscreen = {}; + jQuery(document).data().bp.biomixer_fullscreen.enabled = false; + jQuery(document).data().bp.biomixer_fullscreen.bd_container_h = jQuery("#bd_content .cls-info-container").css("height"); + jQuery(document).data().bp.biomixer_fullscreen.bd_contents_h = jQuery("#bd .bd_content .cls-info-container #contents").css("max-height"); + jQuery(document).data().bp.biomixer_fullscreen.bio_container_h = jQuery("#biomixer_container").css("height"); + jQuery(document).data().bp.biomixer_fullscreen.bd_container_w = jQuery("#bd_content .cls-info-container").css("width"); + jQuery(document).data().bp.biomixer_fullscreen.bio_container_w = jQuery("#biomixer_container").css("width"); + + jQuery(document).data().bp.biomixer_fullscreen = { + maximize: 'hide', + minimize: 'show' + }; + + jQuery(document).data().bp.biomixer_fullscreen.toggle = function(toggle) { + jQuery("#bd_content .sidebar")[toggle](); + jQuery("#bd_content .gutter")[toggle](); + jQuery("#bd_content .cls-info-container .tabs")[toggle](); + jQuery(document).data().bp.biomixer_fullscreen.heights(toggle === 'show'); + } + + jQuery(document).data().bp.biomixer_fullscreen.heights = function(original) { + var height; + if (original) { + jQuery("#bd_content .cls-info-container").css("height", jQuery(document).data().bp.biomixer_fullscreen.bd_container_h); + jQuery("#bd .bd_content .cls-info-container #contents").css("max-height", jQuery(document).data().bp.biomixer_fullscreen.bd_contents_h); + jQuery("#biomixer_container").css("height", jQuery(document).data().bp.biomixer_fullscreen.bio_container_h); + jQuery("#bd_content .cls-info-container").css("width", jQuery(document).data().bp.biomixer_fullscreen.bd_container_w); + jQuery("#biomixer_container").css("padding", 0).css("width", jQuery(document).data().bp.biomixer_fullscreen.bio_container_w); + jQuery("#bd_content").trigger('resize'); + } else { + height = jQuery(window).height() - jQuery("#bd_content .cls-info-container").offset().top - 50; + jQuery("#bd_content .cls-info-container").css("width", "100%").css("height", height); + jQuery("#bd .bd_content .cls-info-container #contents").css("max-height", height); + jQuery("#biomixer_container").css("padding", 0).css("width", "100%").css("height", height); + } + } + + #biomixer_container(style="padding-left: .5%; width: 99.5%; height: 900px;") + %iframe#biomixer_iframe(src=original_src data-src=src_url style="min-height: 700px;" height="100%" width="100%" frameborder="0") diff --git a/app/views/concepts/_child_nodes.html.haml b/app/views/concepts/_child_nodes.html.haml deleted file mode 100644 index 0137fa40a..000000000 --- a/app/views/concepts/_child_nodes.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -- output ="" -- for child in @children - - output << tree_link_to_concept(child: child, ontology_acronym: @ontology.acronym, active_style: '', node: @concept, skos: !@schemes.nil?) - - if child.hasChildren - - output << tree_link_to_children(child: child, acronym: @ontology.acronym, concept_schemes: @schemes) - - output << "" -= raw output diff --git a/app/views/concepts/_date_sorted_list.html.haml b/app/views/concepts/_date_sorted_list.html.haml index 517b37590..95e79bf6e 100644 --- a/app/views/concepts/_date_sorted_list.html.haml +++ b/app/views/concepts/_date_sorted_list.html.haml @@ -1,6 +1,6 @@ = render TreeInfiniteScrollComponent.new(id: 'concepts_date_sorted_list', collection: @concepts, next_url: sorted_by_date_url(@page.nextPage, @concepts.last), current_page: @page.page, next_page: @page.nextPage) do |c| - = render_concepts_by_dates + = render_concepts_by_dates(auto_click: @page.page.eql?(1)) - c.error do The Classes/Concepts didn't define creation or modifications dates with dcterms diff --git a/app/views/concepts/_details.html.haml b/app/views/concepts/_details.html.haml index deb946f82..4b2f98625 100644 --- a/app/views/concepts/_details.html.haml +++ b/app/views/concepts/_details.html.haml @@ -1,4 +1,4 @@ -= turbo_frame_tag 'concept_details' do += turbo_frame_tag @container_id do - schemes_keys = %w[hasTopConcept topConceptOf] - label_xl_set = %w[skos-xl#prefLabel skos-xl#altLabel skos-xl#hiddenLabel] diff --git a/app/views/concepts/_list.html.haml b/app/views/concepts/_list.html.haml index 34f4e9679..e71dfe01a 100644 --- a/app/views/concepts/_list.html.haml +++ b/app/views/concepts/_list.html.haml @@ -5,9 +5,11 @@ auto_click: @auto_click) do |c| - concepts = c.collection.sort_by{|concept| [concept.prefLabel&.capitalize || concept.id]} - if concepts && !concepts.empty? - = raw tree_link_to_concept(child: concepts.shift, ontology_acronym: @ontology.acronym, active_style: c.auto_click? ? 'active' : '') - concepts.each do |concept| - = raw tree_link_to_concept(child: concept, ontology_acronym: @ontology.acronym, active_style: '') + - children_link, data, href = concept_tree_data(@ontology.acronym, concept, request_lang, []) + = render TreeLinkComponent.new(child: concept, href: href, + children_href: '#', selected: concept.id.eql?(concepts.first.id) && c.auto_click?, + target_frame: 'concept_show', data: data) - c.error do The Collection didn't define any member diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 7c4e533f7..48ff7870c 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -173,7 +173,10 @@ %h4 = format_number_abbreviated(@users_count) %p Users - -#.home-section + .home-statistics-link.justify-content-end + = link_to 'See details','/statistics', target: "_blank" + + .home-section .home-statistics-container .home-support-title %p= t('home.support_and_collaborations') diff --git a/app/views/instances/_instance_details.html.haml b/app/views/instances/_instance_details.html.haml index fd16b9a55..901dc1761 100644 --- a/app/views/instances/_instance_details.html.haml +++ b/app/views/instances/_instance_details.html.haml @@ -1,14 +1,14 @@ -%div - - ontology_acronym = params[:ontology_id] - - filter_properties = ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] - %h4 - Details of #{link_to_instance(@instance,ontology_acronym)} of type: #{@instance.types.map {|cls| link_to_class(ontology_acronym,cls)}.join(', ').html_safe} - %table.zebra{style:'width: 80vw'} - %thead += render_in_modal do + %div + - ontology_acronym = params[:ontology_id] + - filter_properties = ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] + %table.table %tr - %th Property name - %th Property value - %tbody + %td + = link_to 'type', "#" + %td{style:"word-break: break-all"} + - type = @instance.types.reject{|x| x['NamedIndividual']} + #{type.map {|cls| link_to_class(ontology_acronym,cls)}.join(', ').html_safe} - properties = @instance[:properties].to_h.select{|k,v| !filter_properties.include? k.to_s} - properties.each do |prop| - if !prop[1].nil? diff --git a/app/views/instances/_instances.html.haml b/app/views/instances/_instances.html.haml index e071aa209..d4c4c364d 100644 --- a/app/views/instances/_instances.html.haml +++ b/app/views/instances/_instances.html.haml @@ -1,6 +1,6 @@ -= turbo_frame_tag 'instances' do += render TurboFrameComponent.new(id: "instances", data: {"turbo-frame-target": "frame"} ) do %div.ontologies_list_container.mt-3 - %instances-table.zebra{id: id, style:"width:100%; position:relative" , 'ontology-acronym': @ontology.acronym ,'class-uri': @instances_concept_id} + %instances-table{id: id, style:"width:100%; position:relative" , 'ontology-acronym': @ontology.acronym ,'class-uri': @instances_concept_id} :javascript $(document).ready(function() { diff --git a/app/views/landscape/index.html.haml b/app/views/landscape/index.html.haml index a8b2b6a67..3913a3a4e 100644 --- a/app/views/landscape/index.html.haml +++ b/app/views/landscape/index.html.haml @@ -1,4 +1,5 @@ =javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/d3/3.4.4/d3.min.js" +=javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js" =javascript_include_tag "d3pie.min" =javascript_include_tag "jqcloud-1.0.4.min" =javascript_include_tag "vis.min" diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml index 4c582f1de..9d8908961 100644 --- a/app/views/layouts/_footer.html.haml +++ b/app/views/layouts/_footer.html.haml @@ -28,4 +28,3 @@ = javascript_include_tag "application" -= render partial: "ga_tracking" diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index b58dfe440..fcf060a9e 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -1,6 +1,7 @@ lang="<%=I18n.locale%>"> + <%= render partial: 'ga_tracking' %> diff --git a/app/views/layouts/_ontology_report_submissions.html.haml b/app/views/layouts/_ontology_report_submissions.html.haml deleted file mode 100644 index 3a85fb0c7..000000000 --- a/app/views/layouts/_ontology_report_submissions.html.haml +++ /dev/null @@ -1,48 +0,0 @@ -:javascript - jQuery(document).ready(function() { - jQuery("#version_toggle").click(function(event) { - event.preventDefault(); - jQuery("#ontology_versions tr.hidden_select").toggleClass("hidden_ont"); - }); - - jQuery("#version_toggle").toggle( - function() { - jQuery(this).text("less..."); - }, - function() { - jQuery(this).text("more..."); - }); - }); -%h1.tab_header Ontology Submissions for #{@acronym} -- if @ontology.nil? - %p{:style => "color:red;padding:20px 13px 20px 13px;"} - Ontology #{@acronym} does not appear to exist in the system. It may have been deleted. Please refresh your browser window. -- else - %table#ontology_versions.zebra{:cellpadding => "0", :cellspacing => "0", width: "100%"} - %thead - %tr - %th Submission - %th Release Date - %th Upload Date - %th Actions - - begin - - submission_ready = @ontology.explore.latest_submission({:include_status => 'ready'}) - - submission_readyId = submission_ready.submissionId unless submission_ready.nil? - - rescue - - submission_readyId = -1 - - @submissions.each_with_index do |sub, index| - - hidden_row_class = index >= 10 ? "hidden_ont hidden_select" : "" - - %td - = raw status_link(sub, sub.submissionId == submission_readyId, target="_blank") - %td - = xmldatetime_to_date(sub.released) - %td - = xmldatetime_to_date(sub.creationDate) - %td - %a{:href => "javascript:;", :class => "link_button ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only", :onclick => "DeleteSubmission.act('#{@acronym}', #{sub.submissionId});"} - %span{:class => "ui-button-text"} Delete - - if @submissions.length >= 10 - %tr - %td{:colspan => 4, :style => "text-align: right;"} - %a#version_toggle{:href => "javascript:;"} more... diff --git a/app/views/layouts/_ontology_viewer.html.haml b/app/views/layouts/_ontology_viewer.html.haml index 7fc73d60d..e7f3010e1 100644 --- a/app/views/layouts/_ontology_viewer.html.haml +++ b/app/views/layouts/_ontology_viewer.html.haml @@ -5,16 +5,6 @@ = render :partial =>'layouts/header' %div#bd.bg-white.ontology-viewer :javascript - // Javascript History HTML5 API - if ( typeof JSON === 'undefined' ) { - var - url = '/javascripts/history/json2.js', - scriptEl = document.createElement('script'); - scriptEl.type = 'text/javascript'; - scriptEl.src = url; - document.body.appendChild(scriptEl); - } - // Ontology viewer vars jQuery(document).data().bp.ont_viewer = {}; jQuery(document).data().bp.ont_viewer.ontology_id = jQuery(document).data().bp.ontology.acronym; diff --git a/app/views/layouts/appliance.html.haml b/app/views/layouts/appliance.html.haml index dff0e0108..817bd3026 100644 --- a/app/views/layouts/appliance.html.haml +++ b/app/views/layouts/appliance.html.haml @@ -1,6 +1,7 @@ !!! Strict %html %head + = render partial: 'ga_tracking' diff --git a/app/views/layouts/minimal.html.erb b/app/views/layouts/minimal.html.erb deleted file mode 100644 index 99fe12457..000000000 --- a/app/views/layouts/minimal.html.erb +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - <%= csrf_meta_tag %> - <%if @title.nil?%><%=$ORG_SITE%><%else%><%="#{@title} | #{$ORG_SITE}"%><%end%> - - - <%= stylesheet_link_tag "https://use.fontawesome.com/releases/v5.2.0/css/all.css", integrity: "sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ", crossorigin: "anonymous" %> - <%= stylesheet_link_tag "application" %> - <%=render partial: 'layouts/js_data'%> - <%=render partial: 'layouts/head_js'%> - - - -
          - - <%if flash[:notice] %> -
          -

          - <%=flash[:notice] %> -

          -
          - <%end %> - -
          - <% message_name = "" %> - <% unless $SITE_NOTICE.nil? || $SITE_NOTICE.empty? %> - <% $SITE_NOTICE.each_key { |k| message_name = k } %> - <% unless cookies[message_name.to_sym].eql?("true") %> - -
          - <%=$SITE_NOTICE[message_name.to_sym]%>  [close] -
          - <% end %> - <% end %> - - <%=yield%> -
          - - - - diff --git a/app/views/layouts/popup.html.erb b/app/views/layouts/popup.html.erb index 86a2b772e..7262ec44f 100644 --- a/app/views/layouts/popup.html.erb +++ b/app/views/layouts/popup.html.erb @@ -1,6 +1,7 @@ + <%= render partial: "ga_tracking" %> diff --git a/app/views/mappings/_count.html.haml b/app/views/mappings/_count.html.haml index a0a244cec..3b9ecd3bf 100644 --- a/app/views/mappings/_count.html.haml +++ b/app/views/mappings/_count.html.haml @@ -11,11 +11,8 @@ - @mapping_counts.each do |mapping_count| - t.row do |r| - r.td do - %a.facebox{href: "/mappings/show_mappings?id=#{@ontology_acronym}&target=#{mapping_count[:target_ontology].id}&height=600&width=800"}= mapping_count[:target_ontology].name + - title = mapping_count[:target_ontology].name + = link_to_modal title, mappings_show_mappings_path(id: @ontology_acronym ,target: mapping_count[:target_ontology].id), data: { show_modal_title_value: title, show_modal_size_value: 'modal-xl'} - r.td do = number_with_delimiter(mapping_count[:count], delimiter: ',') -:javascript - $(document).ready(() => { - $("#mapping_count_table a.facebox").facebox() - }) diff --git a/app/views/mappings/_show.html.haml b/app/views/mappings/_show.html.haml index b085b77ab..72e787d72 100644 --- a/app/views/mappings/_show.html.haml +++ b/app/views/mappings/_show.html.haml @@ -1,29 +1,30 @@ -#mappings.paginate_ajax{:style => "overflow: auto; max-height: 600px; width: 800px;"} - #mapping_results - = will_paginate @page_results, :update => 'mappings', :params => { :target => params[:target] } - - if @mappings.nil? or @mappings.empty? - No mappings found - - else - %table.zebra.w-100 - %thead - %th #{@ontology_name} - %th #{@target_ontology_name} - %th Source - - for map in @mappings - %tr - - map.classes.each do |cls| += render_in_modal do + #mappings.paginate_ajax{:style => "overflow: auto; max-height: 600px;"} + #mapping_results + = will_paginate @page_results, :update => 'mappings', :params => { :target => params[:target] } + - if @mappings.nil? or @mappings.empty? + No mappings found + - else + %table.zebra.w-100 + %thead + %th #{@ontology_name} + %th #{@target_ontology_name} + %th Source + - for map in @mappings + %tr + - map.classes.each do |cls| + %td + - if inter_portal_mapping?(cls) + = ajax_to_inter_portal_cls(cls) + - elsif internal_mapping?(cls) + = ajax_to_internal_cls(cls) + - else + = ajax_to_external_cls(cls) %td - - if inter_portal_mapping?(cls) - = ajax_to_inter_portal_cls(cls) - - elsif internal_mapping?(cls) - = ajax_to_internal_cls(cls) - - else - = ajax_to_external_cls(cls) - %td - #{map.source} #{(map.process || {})[:source_name]} - = will_paginate @page_results, :update => 'mappings', :params => { :target => params[:target] } + #{map.source} #{(map.process || {})[:source_name]} + = will_paginate @page_results, :update => 'mappings', :params => { :target => params[:target] } -:javascript - jQuery(document).ready(function(){ - ajax_process_init(); // see bp_ajax_controller.js - }) \ No newline at end of file + :javascript + jQuery(document).ready(function(){ + ajax_process_init(); // see bp_ajax_controller.js + }) \ No newline at end of file diff --git a/app/views/margin_notes/_proposal.html.erb b/app/views/margin_notes/_proposal.html.erb deleted file mode 100644 index cacd1d0cf..000000000 --- a/app/views/margin_notes/_proposal.html.erb +++ /dev/null @@ -1,94 +0,0 @@ - - -
          -
          - Current Value -<%=@old_value%> -
          - -
          - -
          - - -
          -<%=draw_note_tree(@margin_notes)%> -
          - - -
          -');toggleHide('proposalForm','forms');toggleHide('form','');toggleHide('buttons','');document.getElementById('noteParent').value='';"> -<%if @modal%><%end%> -
          -
          - - diff --git a/app/views/margin_notes/_show.html.erb b/app/views/margin_notes/_show.html.erb deleted file mode 100644 index c30e2a084..000000000 --- a/app/views/margin_notes/_show.html.erb +++ /dev/null @@ -1,127 +0,0 @@ - - -<% -uniq = Time.now.to_i -javascripts = "resetNoteForm(#{uniq});" - unless @concept.nil? - javascripts << "refreshCache('#{@concept.id}');" - end - -%> - -<%# to keep IDs from gettin mixed up%> -<%if @modal%> -