From 6e87b96f34c66e106c77d5adb86d7692372a2b32 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 01:09:08 +0200 Subject: [PATCH 01/72] simplify the header component to use content instead of a new section --- app/components/display/header_component.rb | 4 ++-- app/views/home/tools.html.haml | 11 +++++++++++ .../ontologies/sections/_metadata.html.haml | 16 +++++++--------- 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 app/views/home/tools.html.haml diff --git a/app/components/display/header_component.rb b/app/components/display/header_component.rb index b814f55fc1..4542410196 100644 --- a/app/components/display/header_component.rb +++ b/app/components/display/header_component.rb @@ -2,7 +2,7 @@ class Display::HeaderComponent < ViewComponent::Base - renders_one :text + def initialize(text: nil, tooltip: nil) super @@ -12,7 +12,7 @@ def initialize(text: nil, tooltip: nil) def call content_tag(:div, class: 'header-component') do - out = content_tag(:p, text || @text) + out = content_tag(:p, content&.html_safe || @text) if @info && !@info.empty? out = out + render(Display::InfoTooltipComponent.new(text: @info)) end diff --git a/app/views/home/tools.html.haml b/app/views/home/tools.html.haml new file mode 100644 index 0000000000..d4f0455b69 --- /dev/null +++ b/app/views/home/tools.html.haml @@ -0,0 +1,11 @@ +%div.d-flex.px-5.py-3.rounded.tools-container + - @tools.each_value do |tool| + = link_to tool[:link], class: "mx-2 d-block", style: "width: 35%" do + = render Layout::CardComponent.new do |c| + - c.header do |h| + %div.d-flex.flex-column.align-items-center.text-primary.pt-4 + = inline_svg_tag(tool[:icon], height: "50px", width: "50px") + %h3.text-primary.mt-2 + = tool[:title] + %p.px-4.tool-description + = tool[:description] diff --git a/app/views/ontologies/sections/_metadata.html.haml b/app/views/ontologies/sections/_metadata.html.haml index 3f84c40cf3..c037d11151 100755 --- a/app/views/ontologies/sections/_metadata.html.haml +++ b/app/views/ontologies/sections/_metadata.html.haml @@ -21,7 +21,7 @@ = properties_dropdown('person_and_organization',t("ontologies.sections.person_and_organization"),'', @agents_properties) do |values| = horizontal_list_container(values) do |v| = agent_chip_component(v) - + = properties_dropdown('link',t("ontologies.sections.other_links"), t("ontologies.sections.info_tooltip_links") , @links_properties) do |values| = horizontal_list_container(values) do |v| = render LinkFieldComponent.new(value: v, raw: true) @@ -50,11 +50,10 @@ = Array(@content_properties['metadataVoc']).map{|x| metadata_vocabulary_display(x)}.join.html_safe = render Layout::CardComponent.new do |c| - c.header do |h| - - h.text do - = t("ontologies.sections.visits") - - if visits_data(@ontology) - = link_to(@ontology.links["analytics"] + "?apikey=#{get_apikey}&format=csv", title: t("ontologies.sections.download_as_csv")) do - = inline_svg("summary/download.svg", width: '30px', height: '20px') + = t("ontologies.sections.visits") + - if visits_data(@ontology) + = link_to(@ontology.links["analytics"] + "?apikey=#{get_apikey}&format=csv", title: t("ontologies.sections.download_as_csv")) do + = inline_svg("summary/download.svg", width: '30px', height: '20px') = render Layout::ListComponent.new do |l| - l.row do @@ -63,9 +62,8 @@ - unless @ontology.view? = render Layout::CardComponent.new do |d| - d.header do |h| - - h.text do - = t("ontologies.sections.views", acronym: @ontology.acronym) - = new_element_link(t("ontologies.sections.create_new_view"), new_view_path(@ontology.id)) + = t("ontologies.sections.views", acronym: @ontology.acronym) + = new_element_link(t("ontologies.sections.create_new_view"), new_view_path(@ontology.id)) = render Layout::ListComponent.new do |l| - l.row do = render partial: 'ontology_views' From f3870cda9c0f07230eafcb5ec5126ded37419c0b Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 01:11:20 +0200 Subject: [PATCH 02/72] update dropdown component to a custom title section instead of text --- app/components/dropdown_container_component.rb | 5 ++++- .../dropdown_container_component.html.haml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/components/dropdown_container_component.rb b/app/components/dropdown_container_component.rb index e06b3fa7ac..f9d8ce38d2 100644 --- a/app/components/dropdown_container_component.rb +++ b/app/components/dropdown_container_component.rb @@ -2,13 +2,16 @@ class DropdownContainerComponent < ViewComponent::Base renders_one :empty_state - def initialize(title:, id:, tooltip:nil, is_open: false) + renders_one :title + + def initialize(title: nil, id:, tooltip:nil, is_open: false) super @title = title @id = id @tooltip = tooltip @is_open = is_open end + def open_class @is_open ? "show" : "" end diff --git a/app/components/dropdown_container_component/dropdown_container_component.html.haml b/app/components/dropdown_container_component/dropdown_container_component.html.haml index 7b9d4965e8..398f3199a0 100644 --- a/app/components/dropdown_container_component/dropdown_container_component.html.haml +++ b/app/components/dropdown_container_component/dropdown_container_component.html.haml @@ -1,6 +1,9 @@ .dropdown-container .dropdown-title-bar{"data-toggle" => "collapse", "data-target" => "##{@id}"} - = render Display::HeaderComponent.new(text: @title, tooltip: @tooltip) + - if title? + = title + - else + = render Display::HeaderComponent.new(text: @title, tooltip: @tooltip) = image_tag("summary/arrow-down.svg", class: 'ml-2') .collapse{id: @id, class: open_class} From b21d74bed3544dee898b9809a0474062f6ff55f8 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 01:20:41 +0200 Subject: [PATCH 03/72] update browse page to use Dropdown component not bootsrap one --- app/views/ontologies/browser/browse.html.haml | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index cc2f9c982b..63c23ec429 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -50,21 +50,22 @@ - @filters.each do |key, values| - if session[:user]&.admin? || key != :missingStatus - .browse-filter{data:{controller: "show-filter-count browse-filters", action: "change->show-filter-count#updateCount change->browse-filters#dispatchFilterEvent"}, id: "#{key}_filter_container", style: "#{"border-color: var(--admin-color);" if key == :missingStatus}"} - .browse-filter-title-bar{"data-target" => "#browse-#{key}-filter", "data-toggle" => "collapse"} - %p - = browse_filter_section_label(key) - %span.badge.badge-primary{"data-show-filter-count-target":"countSpan", style: "#{values[2] && values[2].positive? ? '' : 'display: none;'}"} - = values[2] - = inline_svg_tag 'arrow-down.svg' - .collapse{id: "browse-#{key}-filter", class: "#{values[2].positive? ? 'show': ''}"} - .browse-filter-checks-container + %div{data:{controller: "show-filter-count browse-filters", action: "change->show-filter-count#updateCount change->browse-filters#dispatchFilterEvent"}, id: "#{key}_filter_container"} + = render DropdownContainerComponent.new(id: "browse-#{key}-filter", is_open: values[2].positive?) do |d| + - d.title do + = render Display::HeaderComponent.new do + %span + = browse_filter_section_label(key) + %span.badge.badge-primary{"data-show-filter-count-target":"countSpan", style: "#{values[2] && values[2].positive? ? '' : 'display: none;'}"} + = values[2] + + .browse-filter-checks-container.px-1 - values.first.each do |object| - title = (key.eql?(:categories) || key.eql?(:groups)) ? nil : '' - = group_chip_component(name: key, object: object, checked: values[1]&.include?(object["id"]), title: title) do |c| + = group_chip_component(name: key, object: object, checked: [link_last_part(object["id"]),link_last_part(object["value"])].include?(values[1]) , title: title) do |c| - c.count do %span.badge.badge-light.ml-1 - = turbo_frame_tag "count_#{key}_#{object["id"]}", busy: true + = turbo_frame_tag "count_#{key}_#{link_last_part(object["id"])}", busy: true %span.show-if-loading = render LoaderComponent.new(small:true) From a4759c89676b19a903d532e920afb415728036fc Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 01:21:01 +0200 Subject: [PATCH 04/72] add color option to square badge component --- app/components/square_badge_component.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/components/square_badge_component.rb b/app/components/square_badge_component.rb index 9d85a8c34f..bfbc95c6db 100644 --- a/app/components/square_badge_component.rb +++ b/app/components/square_badge_component.rb @@ -2,15 +2,17 @@ class SquareBadgeComponent < ViewComponent::Base - def initialize(label: , count: ,link: nil) + def initialize(label: , count: ,link: nil, color: nil) @label = label @count = count @link = link + @color = color end + def call return if @count.to_i.zero? - link_to(@link, class: 'browse-onology-card', 'data-turbo' => 'false') do + link_to(@link, class: 'browse-onology-card', 'data-turbo' => 'false', style: @color ? "color: #{@color} !important; border-color: #{@color}" : "") do concat(content_tag(:p, @count, class: 'browse-card-number')) concat(content_tag(:p, @label, class: 'browse-card-text')) end From 273331232d61c999923427ef2df239669b31d854 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 03:29:09 +0200 Subject: [PATCH 05/72] add text and bg colors options to ontology browse card component --- .../ontology_browse_card_component.rb | 32 +++++++++++++- .../ontology_browse_card_component.html.haml | 42 +++++++++++++------ 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/app/components/ontology_browse_card_component.rb b/app/components/ontology_browse_card_component.rb index 84251d7f0d..78fd781195 100644 --- a/app/components/ontology_browse_card_component.rb +++ b/app/components/ontology_browse_card_component.rb @@ -3,12 +3,42 @@ class OntologyBrowseCardComponent < ViewComponent::Base include OntologiesHelper - def initialize(ontology: nil) + def initialize(ontology: nil, onto_link: nil, text_color: nil, bg_light_color: nil, portal_name: nil) super @ontology = ontology + @text_color = text_color + @bg_light_color = bg_light_color + @onto_link = onto_link || "/ontologies/#{@ontology[:acronym]}" if @ontology + @portal_name = portal_name end def ontology @ontology end + + def external_ontology? + !@portal_name.blank? + end + + def external_portal_name + @portal_name + end + + def onto_link + @onto_link + end + + def style_text + external_ontology? ? "color: #{@text_color} !important" : '' + end + + def portal_color + @text_color + end + alias :color :portal_color + + def style_bg + external_ontology? ? "#{style_text} ; background-color: #{@bg_light_color}" : '' + end + end diff --git a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml index 61daf0e9cb..e82e177c84 100644 --- a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml +++ b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml @@ -3,9 +3,11 @@ .d-flex .browse-ontology-description .browse-ontology-title-bar - %a.browse-ontology-title{:href => "/ontologies/#{ontology[:acronym]}", data: {'turbo': 'false'}} + %a.browse-ontology-title{:href => onto_link, data: {'turbo': 'false'} , style: style_text} = ontology[:name]+" ("+ontology[:acronym]+")" = private_ontology_icon(ontology[:private]) + - if external_ontology? + = render Display::InfoTooltipComponent.new(text: "Federated ontology from #{onto_link}", icon: 'external-link.svg') - if session[:user]&.admin? - ontology_status = status_string(ontology) = render Display::InfoTooltipComponent.new(text: ontology_status, icon: submission_status_icons(ontology_status)) @@ -21,27 +23,32 @@ %p.browse-fair-title = t('components.fair_score') .browse-progress-bar - .browse-faire-progress{:style => "width: #{ontology[:normalizedFairScore].to_s+"%"}"} + .browse-faire-progress{:style => "width: #{ontology[:normalizedFairScore].to_s+"%"}; #{color ? 'background-color:' + color : ''}"} %p.browse-fair-score = ontology[:fairScore] - %a.browse-fair-details{:href => "/ontologies/#{ontology[:acronym]}#fair-details", 'data-turbo': 'false'}= t('components.details_details') + %a.browse-fair-details{:href => "#{onto_link}#fair-details", 'data-turbo': 'false', style: style_text}= t('components.details_details') - .browse-ontology-cards - = render SquareBadgeComponent.new(label: t('components.classes'), count: ontology[:class_count_formatted], link: "/ontologies/#{ontology[:acronym]}?p=classes" ) + .browse-ontology-cards{style: color ? "color: #{color} !important" : ''} + = render SquareBadgeComponent.new(label: t('components.classes'), + count: ontology[:class_count_formatted], + link: "#{onto_link}?p=classes", + color: color) = render SquareBadgeComponent.new(label: ontology[:format] == 'SKOS' ? t('components.concepts') : t('components.instances'), - count: ontology[:individual_count_formatted], - link: "/ontologies/#{ontology[:acronym]}?p=#{ontology[:format] == 'SKOS' ? "classes" : "instances"}") + count: ontology[:individual_count_formatted], color: color, + link: "#{onto_link}?p=#{ontology[:format] == 'SKOS' ? "classes" : "instances"}") - = render SquareBadgeComponent.new(label: t('components.projects'), count: ontology[:project_count], link: "/ontologies/#{ontology[:acronym]}#projects_section" ) + = render SquareBadgeComponent.new(label: t('components.projects'), count: ontology[:project_count], color: color, + link: "#{onto_link}#projects_section" ) - = render SquareBadgeComponent.new(label: t('components.notes'), count: ontology[:note_count], link: "/ontologies/#{ontology[:acronym]}?p=notes" ) + = render SquareBadgeComponent.new(label: t('components.notes'), count: ontology[:note_count], color: color, + link: "#{onto_link}?p=notes" ) .d-flex.align-items-baseline.mt-1 - if ontology[:creationDate] %span.mr-1 - = render ChipButtonComponent.new(type: "clickable") do + = render ChipButtonComponent.new(type: "clickable", style: style_bg) do %span.mr-1= t('components.submitted') %span.browse-uploaded-date{data:{controller: 'timeago', 'timeago-datetime-value': ontology[:creationDate], 'timeago-add-suffix-value': 'true'}} - if ontology[:contact] @@ -52,19 +59,28 @@ - if ontology[:released] - date = render DateTimeFieldComponent.new(value: ontology[:released]) %span{data:{controller:'tooltip'}, title: t('components.creation_date', date: date)} - = render ChipButtonComponent.new(type: "clickable") do + = render ChipButtonComponent.new(type: "clickable", style: style_bg) do = DateTime.parse(date).year rescue date - if ontology[:format] %span.mx-1 - = render ChipButtonComponent.new(type: "clickable") do + = render ChipButtonComponent.new(type: "clickable", style: style_bg) do = ontology[:format] - if ontology_retired?(ontology) %span.mx-1 = ontology_retired_badge(ontology) - if ontology[:viewOfOnt] %span.mx-1{data:{controller:'tooltip'}, title: t('components.view_of_the_ontology', ontology: ontology[:viewOfOnt].split('/').last )} - = render ChipButtonComponent.new(type: "clickable", text: t('components.view')) + = render ChipButtonComponent.new(type: "clickable", text: t('components.view'), style: style_bg) + + - if external_ontology? + %div.mx-1{title: content_tag(:div, "Source #{external_portal_name}"), data:{controller: 'tooltip', 'tooltip-interactive-value': 'true'}} + = render ChipButtonComponent.new(type: "clickable" , style: style_bg) do + %span.d-inline + %span.mr-1 + = inline_svg 'logo-white.svg', width: "20", height: "20" + %span + = external_portal_name - if session[:user]&.admin? %div.mx-1{title: content_tag(:div, debug(ontology), style: 'height: 300px; overflow: scroll'), data:{controller: 'tooltip', 'tooltip-interactive-value': 'true'}} From cc6c288e49b9be17bbbe8035fb35d4fad3e776d9 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 03:30:16 +0200 Subject: [PATCH 06/72] add federation helper code and config sample --- Gemfile | 2 +- Gemfile.lock | 145 +++++++++---------- app/controllers/application_controller.rb | 5 +- app/controllers/ontologies_controller.rb | 5 + app/helpers/federation_helper.rb | 84 +++++++++++ config/bioportal_config_env.rb.sample | 51 ++++++- config/initializers/ontologies_api_client.rb | 1 + 7 files changed, 213 insertions(+), 80 deletions(-) create mode 100644 app/helpers/federation_helper.rb diff --git a/Gemfile b/Gemfile index 00fb43225b..67125fd61e 100644 --- a/Gemfile +++ b/Gemfile @@ -73,7 +73,7 @@ gem 'turnout' gem 'will_paginate', '~> 3.0' gem 'inline_svg' gem "lookbook", '~> 1.5.5' -gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'development' +gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'feature/federate-multiple-apis' gem "flag-icons-rails", "~> 3.4" gem "iso-639", "~> 0.3.6" diff --git a/Gemfile.lock b/Gemfile.lock index c66c92aa87..5b3b140032 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,10 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 674193220f93d99587e6d3a969adcb384caf0a61 - branch: development + revision: 7664229920b93ca163f1ee96b21e49d1323f843c + branch: feature/federate-multiple-apis specs: ontologies_api_client (2.2.0) - activesupport + activesupport (~> 7.0.4) excon faraday faraday-excon (~> 2.0.0) @@ -12,6 +12,8 @@ GIT lz4-ruby multi_json oj + parallel + request_store spawnling (= 2.1.5) GEM @@ -85,21 +87,14 @@ GEM addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) - airbrussh (1.5.1) + airbrussh (1.5.2) sshkit (>= 1.6.1, != 1.7.0) ast (2.4.2) autoprefixer-rails (10.4.16.0) execjs (~> 2) base64 (0.2.0) bcrypt_pbkdf (1.1.0) - better_html (2.0.2) - actionview (>= 6.0) - activesupport (>= 6.0) - ast (~> 2.0) - erubi (~> 1.4) - parser (>= 2.4) - smart_properties - bigdecimal (3.1.6) + bigdecimal (3.1.8) bindata (2.5.0) bindex (0.8.1) bootsnap (1.18.3) @@ -109,10 +104,10 @@ GEM popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) brakeman (5.4.1) - bugsnag (6.26.3) + bugsnag (6.26.4) concurrent-ruby (~> 1.0) builder (3.2.4) - capistrano (3.18.0) + capistrano (3.18.1) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -139,16 +134,17 @@ GEM xpath (~> 3.2) chart-js-rails (0.1.7) railties (> 3.1) + childprocess (5.0.0) coderay (1.1.3) concurrent-ruby (1.2.3) crass (1.0.6) - css_parser (1.16.0) + css_parser (1.17.1) addressable cube-ruby (0.0.3) daemons (1.4.1) dalli (3.2.8) date (3.3.4) - debug (1.9.1) + debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) deepl-rb (2.5.3) @@ -159,7 +155,7 @@ GEM erubi (1.12.0) erubis (2.7.0) eventmachine (1.2.7) - excon (0.109.0) + excon (0.110.0) execjs (2.9.1) faraday (2.0.1) faraday-net_http (~> 2.0) @@ -178,9 +174,9 @@ GEM flamegraph (0.9.5) globalid (1.2.1) activesupport (>= 6.1) - graphql (2.2.10) + graphql (2.3.2) base64 - graphql-client (0.20.0) + graphql-client (0.22.0) activesupport (>= 3.0) graphql (>= 1.13.0) haml (5.2.2) @@ -203,7 +199,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) - i18n (1.14.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) i18n-tasks (1.0.13) activesupport (>= 4.0.2) @@ -225,39 +221,40 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.7.2) - irb (1.11.2) - rdoc + irb (1.13.1) + rdoc (>= 4.0.0) reline (>= 0.4.2) iso-639 (0.3.6) jquery-rails (4.6.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - jquery-ui-rails (6.0.1) + jquery-ui-rails (7.0.0) railties (>= 3.2.16) jsbundling-rails (1.3.0) railties (>= 6.0.0) - json (2.7.1) - json-jwt (1.16.5) + json (2.7.2) + json-jwt (1.16.6) activesupport (>= 4.2) aes_key_wrap base64 bindata faraday (~> 2.0) faraday-follow_redirects - jwt (2.8.0) + jwt (2.8.1) base64 language_server-protocol (3.17.0.3) - launchy (2.5.2) + launchy (3.0.1) addressable (~> 2.8) - letter_opener (1.9.0) - launchy (>= 2.2, < 3) + childprocess (~> 5.0) + letter_opener (1.10.0) + launchy (>= 2.2, < 4) letter_opener_web (2.0.0) actionmailer (>= 5.2) letter_opener (~> 1.7) railties (>= 5.2) rexml - listen (3.8.0) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) loofah (2.22.0) @@ -282,18 +279,18 @@ GEM net-imap net-pop net-smtp - marcel (1.0.2) + marcel (1.0.4) matrix (0.4.2) - method_source (1.0.0) + method_source (1.1.0) mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2024.0206) + mime-types-data (3.2024.0507) mini_mime (1.1.5) - minitest (5.22.2) + minitest (5.22.3) msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.4.0) + multipart-post (2.4.1) mutex_m (0.2.0) mysql2 (0.5.6) net-ftp (0.2.1) @@ -301,7 +298,7 @@ GEM time net-http (0.3.2) uri - net-imap (0.4.10) + net-imap (0.4.11) date net-protocol net-pop (0.1.2) @@ -312,15 +309,13 @@ GEM net-ssh (>= 2.6.5, < 8.0.0) net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) - net-smtp (0.4.0.1) + net-smtp (0.5.0) net-protocol - net-ssh (7.2.1) + net-ssh (7.2.3) netrc (0.11.0) - newrelic_rpm (9.7.1) - nio4r (2.7.0) - nokogiri (1.15.5-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.15.5-x86_64-linux) + newrelic_rpm (9.9.0) + nio4r (2.7.3) + nokogiri (1.15.6-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) @@ -338,11 +333,11 @@ GEM omniauth-github (2.0.1) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) - omniauth-google-oauth2 (1.1.1) + omniauth-google-oauth2 (1.1.2) jwt (>= 2.0) - oauth2 (~> 2.0.6) + oauth2 (~> 2.0) omniauth (~> 2.0) - omniauth-oauth2 (~> 1.8.0) + omniauth-oauth2 (~> 1.8) omniauth-keycloak (1.5.2) faraday json-jwt (> 1.13.0) @@ -359,7 +354,7 @@ GEM omniauth (~> 2.0) open_uri_redirections (0.2.1) parallel (1.24.0) - parser (3.3.0.5) + parser (3.3.1.0) ast (~> 2.4.1) racc popper_js (1.16.1) @@ -367,11 +362,11 @@ GEM coderay (~> 1.1) method_source (~> 1.0) psych (3.3.4) - public_suffix (5.0.4) + public_suffix (5.0.5) puma (5.6.8) nio4r (~> 2.0) racc (1.7.3) - rack (2.2.8.1) + rack (2.2.9) rack-accept (0.4.5) rack (>= 0.4) rack-mini-profiler (3.3.1) @@ -402,7 +397,7 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - rails-i18n (7.0.8) + rails-i18n (7.0.9) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) rails_autolink (1.1.8) @@ -417,42 +412,44 @@ GEM thor (~> 1.0) zeitwerk (~> 2.5) rainbow (3.1.1) - rake (13.1.0) + rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rdoc (6.3.3) + rdoc (6.3.4.1) recaptcha (5.9.0) json redcarpet (3.6.0) regexp_parser (2.9.0) - reline (0.4.2) + reline (0.5.5) io-console (~> 0.5) + request_store (1.7.0) + rack (>= 1.4) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.2.6) - rouge (4.2.0) + rouge (4.2.1) rspec-core (3.13.0) rspec-support (~> 3.13.0) rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.1) + rspec-rails (6.1.2) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) - rspec-core (~> 3.12) - rspec-expectations (~> 3.12) - rspec-mocks (~> 3.12) - rspec-support (~> 3.12) - rspec-support (3.13.0) - rubocop (1.60.2) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.1) + rubocop (1.63.4) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -460,11 +457,11 @@ GEM rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.30.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) ruby_dig (0.0.2) @@ -508,7 +505,8 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sshkit (1.22.0) + sshkit (1.22.2) + base64 mutex_m net-scp (>= 1.1.2) net-sftp (>= 2.1.2) @@ -519,18 +517,18 @@ GEM temple (0.10.3) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - terser (1.2.0) + terser (1.2.2) execjs (>= 0.3.0, < 3) thin (1.8.2) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (1.3.0) + thor (1.3.1) tilt (2.3.0) time (0.3.0) date timeout (0.4.1) - turbo-rails (2.0.4) + turbo-rails (2.0.5) actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) @@ -543,7 +541,7 @@ GEM concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) uri (0.13.0) - version_gem (1.1.3) + version_gem (1.1.4) view_component (2.83.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) @@ -560,12 +558,11 @@ GEM will_paginate (3.3.1) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.34) + yard (0.9.36) zeitwerk (2.6.13) PLATFORMS - x86_64-darwin-23 - x86_64-linux + x86_64-linux-musl DEPENDENCIES bcrypt_pbkdf (>= 1.0, < 2.0) @@ -647,4 +644,4 @@ DEPENDENCIES will_paginate (~> 3.0) BUNDLED WITH - 2.3.23 + 2.4.22 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 964be01714..4aa6d0e7b9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -124,7 +124,7 @@ def domain_ontology_set subdomain = host_parts[0].downcase slices = LinkedData::Client::Models::Slice.all - slices_acronyms = slices.map {|s| s.acronym} + slices_acronyms = slices.map {|s| s.acronym} rescue binding.pry # Set custom ontologies if we're on a subdomain that has them # Else, make sure user ontologies are set appropriately @@ -205,6 +205,9 @@ def rest_url "#{protocol}://#{cleaned_path}" end + def request_portals + helpers.request_portals + end def check_http_file(url) session = Net::HTTP.new(url.host, url.port) diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index 07f1ad9a32..e8f27b3a78 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -23,6 +23,8 @@ class OntologiesController < ApplicationController before_action :authorize_and_redirect, :only => [:edit, :update, :create, :new] before_action :submission_metadata, only: [:show] + before_action :set_federated_portals, only: [:index, :ontologies_filter] + 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" @@ -561,4 +563,7 @@ def determine_layout end end + def set_federated_portals + RequestStore.store[:federated_portals] = params[:portals]&.split(',') + end end diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb new file mode 100644 index 0000000000..0539a40028 --- /dev/null +++ b/app/helpers/federation_helper.rb @@ -0,0 +1,84 @@ +module FederationHelper + + def federated_portals + $FEDERATED_PORTALS + end + + def federated_portal_config(name_key) + $FEDERATED_PORTALS[name_key.to_sym] + end + + def federated_portal_name(key) + config = federated_portal_config(key) + config ? config[:name] : key + end + + def federated_portal_color(key) + config = federated_portal_config(key) + config[:color] if config + end + + def federated_portal_light_color(key) + config = federated_portal_config(key) + config[:'light-color'] if config + end + + + def ontology_portal_config(id) + rest_url = id.split('/')[0..-3].join('/') + $FEDERATED_PORTALS.select{|_, config| config[:api].start_with?(rest_url)}.first + end + + def ontology_portal_name(id) + portal_key, _ = ontology_portal_config(id) + portal_key ? federated_portal_name(portal_key) : 'not found' + end + + + def ontology_portal_color(id) + portal_key, _ = ontology_portal_config(id) + federated_portal_color(portal_key) if portal_key + end + + + def ontoportal_ui_link(id) + portal_key, config = ontology_portal_config(id) + return id unless portal_key + + ui_link = config[:ui] + api_link = config[:api] + + id.gsub(api_link, "#{ui_link}/") rescue id + end + + def internal_ontology?(id) + id.start_with?(helpers.rest_url) + end + + def federated_ontology?(id) + !internal_ontology?(id) + end + + def request_portals + portals = RequestStore.store[:federated_portals] || [] + [portal_name] + portals + end + + def request_portals_names + request_portals.map do |x| + config = federated_portal_config(x) + + if config + name = config[:name] + color = config[:color] + elsif portal_name.downcase.eql?(x.downcase) + name = portal_name + color = nil + else + next nil + end + + content_tag(:span, federated_portal_name(name), style: color ? "color: #{color}" : "", class: color ? "" : "text-primary") + end.compact + end +end diff --git a/config/bioportal_config_env.rb.sample b/config/bioportal_config_env.rb.sample index cb1ae1ccf2..c95bbd6f84 100644 --- a/config/bioportal_config_env.rb.sample +++ b/config/bioportal_config_env.rb.sample @@ -28,10 +28,53 @@ $NCBO_API_KEY = ENV['NCBO_API_KEY'] # Fairness Assessment. $FAIRNESS_DISABLED = ENV['FAIRNESS_DISABLED'] $FAIRNESS_URL = ENV['FAIRNESS_URL'] -# Announcements sympa mailing list REQUEST address, EX: list-request@lists.example.org -$ANNOUNCE_LIST_SERVICE ||= 'SERVICE_EXAMPLE' -$ANNOUNCE_SERVICE_HOST ||= 'service@test.com' -$ANNOUNCE_LIST ||= 'users-list@test' + + +# Federation configuration +$FEDERATED_PORTALS = { + bioportal: { + name: "BioPortal", + api: 'https://data.bioontology.org/', + ui: 'https://bioportal.bioontology.org/', + apikey: '8b5b7825-538d-40e0-9e9e-5ab9274a9aeb', + color: '#234979', + 'light-color': '#E9F2FA', + }, + agroportal: { + name: "AgroPortal", + api: 'https://data.agroportal.lirmm.fr', + ui: 'https://agroportal.lirmm.fr', + apikey: '1de0a270-29c5-4dda-b043-7c3580628cd5', + color: '#349696', + 'light-color': '#F1F6FA', + }, + ecoportal: { + name: "EcoPortal", + api: 'https://data.ecoportal.lifewatch.eu/', + ui: 'https://ecoportal.lifewatch.eu', + apikey: "43a437ba-a437-4bf0-affd-ab520e584719", + color: '#2076C9', + 'light-color': '#E9F2FA', + }, + # earthportal: { + # name: 'EarthPortal', + # api: 'https://earthportal.eu:8443/', + # ui: 'https://earthportal.eu', + # apikey: "c9147279-954f-41bd-b068-da9b0c441288", + # color: '#404696', + # 'light-color': '#404696' + # }, + biodivportal: { + name: "BioDivPortal", + api: 'https://data.biodivportal.gfbio.org/', + ui: 'https://biodivportal.gfbio.org/', + apikey: "47a57aa3-7b54-4f34-b695-dbb5f5b7363e", + color: '#349696', + 'light-color': '#EBF5F5', + } +} + + # Used to define other bioportal that can be mapped to # Example to map to ncbo bioportal : {"ncbo" => {"api" => "http://data.bioontology.org", "ui" => "http://bioportal.bioontology.org", "apikey" => ""} # Then create the mapping using the following class in JSON : "http://purl.bioontology.org/ontology/MESH/C585345": "ncbo:MESH" diff --git a/config/initializers/ontologies_api_client.rb b/config/initializers/ontologies_api_client.rb index ca8f07e9eb..fb2d6f94f2 100644 --- a/config/initializers/ontologies_api_client.rb +++ b/config/initializers/ontologies_api_client.rb @@ -7,4 +7,5 @@ config.debug_client = $DEBUG_RUBY_CLIENT || false config.debug_client_keys = $DEBUG_RUBY_CLIENT_KEYS || [] config.apikey = $API_KEY + config.federated_portals = $FEDERATED_PORTALS end From 8a8bb8e16c173571f794a0fe01626e0c0e73123e Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 03:31:13 +0200 Subject: [PATCH 07/72] add portals filters in the browse page --- app/views/ontologies/browser/browse.html.haml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index 63c23ec429..e02125f724 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -68,6 +68,11 @@ = turbo_frame_tag "count_#{key}_#{link_last_part(object["id"])}", busy: true %span.show-if-loading = render LoaderComponent.new(small:true) + %div{data:{controller: "show-filter-count browse-filters", action: "change->show-filter-count#updateCount change->browse-filters#dispatchFilterEvent"}, id: "portals_filter_container"} + = render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: "Results from external portals") do + .browse-filter-checks-container.px-1 + - federated_portals.each do |key, config| + = group_chip_component(name: "portals", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') .browse-second-row .browse-search-bar From 382582b470564fe1e6d4a7444f0ad64785943c31 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 03:32:25 +0200 Subject: [PATCH 08/72] use federation helpers to get federation ontologies information --- app/helpers/federation_helper.rb | 2 +- app/views/ontologies/browser/_ontologies.html.haml | 13 ++++++++++++- config/locales/en.yml | 2 +- config/locales/fr.yml | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 0539a40028..10d13ec79c 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -43,7 +43,7 @@ def ontology_portal_color(id) def ontoportal_ui_link(id) portal_key, config = ontology_portal_config(id) - return id unless portal_key + return nil unless portal_key ui_link = config[:ui] api_link = config[:api] diff --git a/app/views/ontologies/browser/_ontologies.html.haml b/app/views/ontologies/browser/_ontologies.html.haml index 3e4d3c130f..fec4643834 100644 --- a/app/views/ontologies/browser/_ontologies.html.haml +++ b/app/views/ontologies/browser/_ontologies.html.haml @@ -2,9 +2,20 @@ collection: @ontologies, next_url: ontologies_filter_url(@filters, page: @page.nextPage), current_page: @page.page, next_page: @page.nextPage) do |c| + + - if @page.page.eql?(1) + = content_tag(:p, class: "browse-desc-text", style: "margin-bottom: 12px !important;") do + #{t("ontologies.showing_ontologies_size", ontologies_size: @count, analytics_size: @total_ontologies, portals: request_portals_names.join(', ').html_safe).html_safe} (#{sprintf("%.2f", @time)}s) + - ontologies = c.collection - ontologies.each do |ontology| - = render OntologyBrowseCardComponent.new(ontology: ontology) + - config = ontology_portal_config(ontology[:id])&.last || {} + + = render OntologyBrowseCardComponent.new(ontology: ontology, + portal_name: config[:name], + onto_link: ontoportal_ui_link(ontology[:id]), + text_color: config[:color], + bg_light_color: config[:'light-color']) - c.loader do - ontologies_browse_skeleton - c.error do diff --git a/config/locales/en.yml b/config/locales/en.yml index f437410abb..c03a46fda6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1092,7 +1092,7 @@ en: update_the_current_displayed_content: "will update the current displayed content to all the following submissions:" save: Save ontologies: - showing_ontologies_size: "Showing %{ontologies_size} of %{analytics_size}" + showing_ontologies_size: "Showing %{ontologies_size} of %{analytics_size} from %{portals}" filters: Filters no_license: No license view_license: View license diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 7dbd4254c5..1f9c6e9a74 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1118,7 +1118,7 @@ fr: save: Sauvegarder ontologies: - showing_ontologies_size: "Affichage de %{ontologies_size} sur %{analytics_size}" + showing_ontologies_size: "Affichage de %{ontologies_size} sur %{analytics_size} de %{portals}" filters: Filtres no_license: Pas de licence view_license: Voir la licence From 6ab2e86430708e558aa236bb98dd808707e13d45 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 03:33:49 +0200 Subject: [PATCH 09/72] remove the filter using index code from submission filter as no used --- app/controllers/concerns/submission_filter.rb | 104 ++++++++++++++++-- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index 6cda414068..001634886e 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -28,16 +28,28 @@ def submissions_paginate_filter(params) @fair_scores = fairness_service_enabled? ? get_fair_score('all') : nil submissions = submissions.reject { |sub| sub.ontology.nil? }.map { |sub| ontology_hash(sub) } - if @selected_sort_by.eql?('visits') - submissions = submissions.sort_by { |x| -x[:popularity] } - elsif @selected_sort_by.eql?('fair') - submissions = submissions.sort_by { |x| -x[:fairScore] } - elsif @selected_sort_by.eql?('notes') - submissions = submissions.sort_by { |x| -x[:note_count] } - elsif @selected_sort_by.eql?('projects') - submissions = submissions.sort_by { |x| -x[:project_count] } - end - submissions + @total_ontologies = @ontologies.size + + params = { query: @search, + status: request_params[:status], + show_views: @show_views, + private_only: @show_private_only, + languages: request_params[:naturalLanguage], + page_size: @total_ontologies, + formality_level: request_params[:hasFormalityLevel], + is_of_type: request_params[:isOfType], + groups: request_params[:group], categories: request_params[:hasDomain], + formats: request_params[:hasOntologyLanguage] } + + submissions = filter_using_data(@ontologies, **params) + + submissions = sort_submission_by(submissions, @sort_by, @search) + + @page = paginate_submissions(submissions, request_params[:page].to_i, request_params[:pagesize].to_i) + + count = @page.page.eql?(1) ? count_objects(submissions) : {} + + [@page.collection, @page.totalCount, count, filter_params] end def ontologies_filter_url(filters, page: 1, count: false) @@ -46,6 +58,78 @@ def ontologies_filter_url(filters, page: 1, count: false) private + def filter_using_data(ontologies, query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) + submissions = LinkedData::Client::Models::OntologySubmission.all(include: BROWSE_ATTRIBUTES.join(','), also_include_views: true, display_links: false, display_context: false) + submissions = ontologies.map { |ont| ontology_hash(ont, submissions) } + + submissions.map do |s| + out = ((s.ontology.viewingRestriction.eql?('public') && !private_only) || private_only && s.ontology.viewingRestriction.eql?('private')) + out = out && (groups.blank? || (s.ontology.group.map { |x| helpers.link_last_part(x) } & groups.split(',')).any?) + out = out && (categories.blank? || (s.ontology.hasDomain.map { |x| helpers.link_last_part(x) } & categories.split(',')).any?) + out = out && (status.blank? || status.eql?('alpha,beta,production,retired') || status.split(',').include?(s.status)) + out = out && (formats.blank? || formats.split(',').any? { |f| s.hasOntologyLanguage.eql?(f) }) + out = out && (is_of_type.blank? || is_of_type.split(',').any? { |f| helpers.link_last_part(s.isOfType).eql?(f) }) + out = out && (formality_level.blank? || formality_level.split(',').any? { |f| helpers.link_last_part(s.hasFormalityLevel).eql?(f) }) + out = out && (languages.blank? || languages.split(',').any? { |f| s.naturalLanguage.any? { |n| helpers.link_last_part(n).eql?(f) } }) + out = out && (s.ontology.viewOf.blank? || (show_views && !s.ontology.viewOf.blank?)) + + out = out && (query.blank? || [s.description, s.ontology.name, s.ontology.acronym].any? { |x| (x|| '').downcase.include?(query.downcase) }) + + if out + s[:rank] = 0 + + next s if query.blank? + + s[:rank] += 3 if s.ontology.acronym && s.ontology.acronym.downcase.include?(query.downcase) + + s[:rank] += 2 if s.ontology.name && s.ontology.name.downcase.include?(query.downcase) + + s[:rank] += 1 if s.description && s.description.downcase.include?(query.downcase) + + s + else + nil + end + + end.compact + end + + def paginate_submissions(all_submissions, page, size) + current_page = page + page_size = size + + start_index = (current_page - 1) * page_size + end_index = start_index + page_size - 1 + next_page = current_page * page_size < all_submissions.size ? current_page + 1 : nil + OpenStruct.new(page: current_page, nextPage: next_page, totalCount: all_submissions.size, + collection: all_submissions[start_index..end_index]) + end + + def sort_submission_by(submissions, sort_by, query = nil) + return submissions.sort_by { |x| x[:rank] ? -x[:rank] : 0} unless query.blank? + + if sort_by.eql?('visits') + submissions = submissions.sort_by { |x| -(x[:popularity] || 0) } + elsif sort_by.eql?('fair') + submissions = submissions.sort_by { |x| -x[:fairScore] } + elsif sort_by.eql?('notes') + submissions = submissions.sort_by { |x| -x[:note_count] } + elsif sort_by.eql?('projects') + submissions = submissions.sort_by { |x| -x[:project_count] } + elsif sort_by.eql?('metrics_classes') + submissions = submissions.sort_by { |x| -x[:class_count] } + elsif sort_by.eql?('metrics_individuals') + submissions = submissions.sort_by { |x| -x[:individual_count] } + elsif sort_by.eql?('creationDate') + submissions = submissions.sort_by { |x| x[:creationDate] || '' }.reverse + elsif sort_by.eql?('released') + submissions = submissions.sort_by { |x| x[:released] || '' }.reverse + elsif sort_by.eql?('ontology_name') + submissions = submissions.sort_by { |x| -x[:name] } + end + submissions + end + def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pagesize: 5) request_params = { display_links: false, display_context: false, include: includes, include_status: 'RDF' } From 5f8de42a0f4308ff9bbd40a2eba1790fcf1f0f5f Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 03:35:03 +0200 Subject: [PATCH 10/72] refactor the filter_using_data function to be faster by using an hash --- app/controllers/concerns/submission_filter.rb | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index 001634886e..19c318130c 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -60,31 +60,34 @@ def ontologies_filter_url(filters, page: 1, count: false) def filter_using_data(ontologies, query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) submissions = LinkedData::Client::Models::OntologySubmission.all(include: BROWSE_ATTRIBUTES.join(','), also_include_views: true, display_links: false, display_context: false) + + submissions = submissions.map { |x| [x[:ontology][:id], x] }.to_h rescue binding.pry + submissions = ontologies.map { |ont| ontology_hash(ont, submissions) } submissions.map do |s| - out = ((s.ontology.viewingRestriction.eql?('public') && !private_only) || private_only && s.ontology.viewingRestriction.eql?('private')) - out = out && (groups.blank? || (s.ontology.group.map { |x| helpers.link_last_part(x) } & groups.split(',')).any?) - out = out && (categories.blank? || (s.ontology.hasDomain.map { |x| helpers.link_last_part(x) } & categories.split(',')).any?) - out = out && (status.blank? || status.eql?('alpha,beta,production,retired') || status.split(',').include?(s.status)) - out = out && (formats.blank? || formats.split(',').any? { |f| s.hasOntologyLanguage.eql?(f) }) - out = out && (is_of_type.blank? || is_of_type.split(',').any? { |f| helpers.link_last_part(s.isOfType).eql?(f) }) - out = out && (formality_level.blank? || formality_level.split(',').any? { |f| helpers.link_last_part(s.hasFormalityLevel).eql?(f) }) - out = out && (languages.blank? || languages.split(',').any? { |f| s.naturalLanguage.any? { |n| helpers.link_last_part(n).eql?(f) } }) - out = out && (s.ontology.viewOf.blank? || (show_views && !s.ontology.viewOf.blank?)) - - out = out && (query.blank? || [s.description, s.ontology.name, s.ontology.acronym].any? { |x| (x|| '').downcase.include?(query.downcase) }) + out = ((s[:ontology].viewingRestriction.eql?('public') && !private_only) || private_only && s[:ontology].viewingRestriction.eql?('private')) + out = out && (groups.blank? || (s[:ontology].group.map { |x| helpers.link_last_part(x) } & groups.split(',')).any?) + out = out && (categories.blank? || (s[:ontology].hasDomain.map { |x| helpers.link_last_part(x) } & categories.split(',')).any?) + out = out && (status.blank? || status.eql?('alpha,beta,production,retired') || status.split(',').include?(s[:status])) + out = out && (formats.blank? || formats.split(',').any? { |f| s[:hasOntologyLanguage].eql?(f) }) + out = out && (is_of_type.blank? || is_of_type.split(',').any? { |f| helpers.link_last_part(s[:isOfType]).eql?(f) }) + out = out && (formality_level.blank? || formality_level.split(',').any? { |f| helpers.link_last_part(s[:hasFormalityLevel]).eql?(f) }) + out = out && (languages.blank? || languages.split(',').any? { |f| Array(s[:naturalLanguage]).any? { |n| helpers.link_last_part(n).eql?(f) } }) + out = out && (s[:ontology].viewOf.blank? || (show_views && !s[:ontology].viewOf.blank?)) + + out = out && (query.blank? || [s[:description], s[:ontology].name, s[:ontology].acronym].any? { |x| (x || '').downcase.include?(query.downcase) }) if out s[:rank] = 0 next s if query.blank? - s[:rank] += 3 if s.ontology.acronym && s.ontology.acronym.downcase.include?(query.downcase) + s[:rank] += 3 if s[:ontology].acronym && s[:ontology].acronym.downcase.include?(query.downcase) - s[:rank] += 2 if s.ontology.name && s.ontology.name.downcase.include?(query.downcase) + s[:rank] += 2 if s[:ontology].name && s[:ontology].name.downcase.include?(query.downcase) - s[:rank] += 1 if s.description && s.description.downcase.include?(query.downcase) + s[:rank] += 1 if s[:description] && s[:description].downcase.include?(query.downcase) s else @@ -183,13 +186,15 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages def ontology_hash(sub) o = {} - ont = sub.ontology + sub = submissions[ont.id] add_ontology_attributes(o, ont) add_submission_attributes(o, sub) add_fair_score_metrics(o, ont) - if sub.metrics + o[:hasOntologyLanguage] = sub&.hasOntologyLanguage + + if sub&.metrics && !sub.metrics.is_a?(String) o[:class_count] = sub.metrics.classes o[:individual_count] = sub.metrics.individuals else @@ -199,18 +204,10 @@ def ontology_hash(sub) o[:class_count_formatted] = number_with_delimiter(o[:class_count], delimiter: ',') o[:individual_count_formatted] = number_with_delimiter(o[:individual_count], delimiter: ',') - o[:note_count] = ont.notes.length - o[:project_count] = ont.projects.length + o[:note_count] = ont.notes&.length || 0 + o[:project_count] = ont.projects&.length || 0 o[:popularity] = @analytics[ont.acronym] || 0 - - # if o[:type].eql?('ontology_view') - # unless ontologies_hash[ont.viewOf].blank? - # o[:viewOfOnt] = { - # name: ontologies_hash[ont.viewOf].name, - # acronym: ontologies_hash[ont.viewOf].acronym - # } - # end - # end + o[:rank] = sub ? sub[:rank] : 0 o end From 4dd2740b650fd7c7a1b5ed15efd4abc38d1257f1 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 17:07:37 +0200 Subject: [PATCH 11/72] update the browse analytics cache to change depending on portals --- app/controllers/concerns/submission_filter.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index 19c318130c..c77100ac1d 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -24,6 +24,12 @@ def submissions_paginate_filter(params) submissions = LinkedData::Client::Models::OntologySubmission.all(request_params) @analytics = helpers.ontologies_analytics + @analytics = Rails.cache.fetch("ontologies_analytics-#{Time.now.year}-#{Time.now.month}-#{request_portals.join('-')}") do + helpers.ontologies_analytics + end + + @ontologies = LinkedData::Client::Models::Ontology.all(include: 'all', also_include_views: true, display_links: false, display_context: false) + # get fair scores of all ontologies @fair_scores = fairness_service_enabled? ? get_fair_score('all') : nil submissions = submissions.reject { |sub| sub.ontology.nil? }.map { |sub| ontology_hash(sub) } From 692c36e9d749877ed9e0850ac3a5cd067c2ec1bb Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 17:09:34 +0200 Subject: [PATCH 12/72] add categories and groups ids on hover to know its origin when federated --- Gemfile.lock | 6 +++--- app/helpers/application_helper.rb | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5b3b140032..943e7420f4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 7664229920b93ca163f1ee96b21e49d1323f843c + revision: 0db56d748a1574450ebeb5f67429cd0b766cf932 branch: feature/federate-multiple-apis specs: ontologies_api_client (2.2.0) @@ -421,7 +421,7 @@ GEM json redcarpet (3.6.0) regexp_parser (2.9.0) - reline (0.5.5) + reline (0.5.6) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) @@ -449,7 +449,7 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.1) - rubocop (1.63.4) + rubocop (1.63.5) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cddb64a7e9..3376fa564e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -32,6 +32,7 @@ def url_to_endpoint(url) def resolve_namespaces RESOLVE_NAMESPACE end + def ontologies_analytics data = LinkedData::Client::Analytics.last_month.onts data.map{|x| [x[:ont].to_s, x[:views]]}.to_h @@ -251,7 +252,7 @@ def chips_component(id: , name: , label: , value: , checked: false , tooltip: ni end def group_chip_component(id: nil, name: , object: , checked: , value: nil, title: nil, &block) - title ||= object["name"] + title ||= "#{object["name"]} (#{object["id"]})" value ||= (object["value"] || object["acronym"] || object["id"]) chips_component(id: id || value, name: name, label: object["acronym"], From 2910b1336e32d83c3b9320249ef07e64a7d351f6 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 9 May 2024 17:11:14 +0200 Subject: [PATCH 13/72] use the last part of ids for browse counts independently of its origin --- Gemfile.lock | 1 + app/controllers/concerns/submission_filter.rb | 24 +++++++++++++++---- app/controllers/ontologies_controller.rb | 13 ++++++---- config/initializers/ontologies_api_client.rb | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 943e7420f4..2964125742 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -562,6 +562,7 @@ GEM zeitwerk (2.6.13) PLATFORMS + x86_64-linux x86_64-linux-musl DEPENDENCIES diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index c77100ac1d..cc261412c2 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -186,6 +186,18 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages end end + unless params[:sort_by].blank? + @filters[:sort_by] = params[:sort_by] + end + + unless params[:search].blank? + @filters[:search] = params[:search] + end + + unless params[:portals].blank? + @filters[:portals] = params[:portals] + end + request_params.delete(:order_by) if %w[visits fair].include?(request_params[:sort_by].to_s) request_params end @@ -212,7 +224,7 @@ def ontology_hash(sub) o[:note_count] = ont.notes&.length || 0 o[:project_count] = ont.projects&.length || 0 - o[:popularity] = @analytics[ont.acronym] || 0 + o[:popularity] = @analytics[ont.id.to_s] || 0 o[:rank] = sub ? sub[:rank] : 0 o @@ -321,11 +333,13 @@ def object_checks(key) end def object_filter(objects, object_name, name_key = 'acronym') - checks = object_checks(object_name) || [] - checks = checks.map { |x| check_id(x, objects, name_key) }.compact + checks = params[object_name]&.split(',') || [] + checks = checks.map { |x| helpers.link_last_part(check_id(x, objects, name_key)) }.compact - ids = objects.map { |x| x['id'] } + objects.uniq! { |x| helpers.link_last_part(x['id']) } + ids = objects.map { |x| helpers.link_last_part(x['id']) } count = ids.count { |x| checks.include?(x) } + [objects, checks, count] end @@ -346,6 +360,8 @@ def count_objects(ontologies) object_names.each do |name| values = Array(ontology[name]) values.each do |v| + v = helpers.link_last_part(v) + objects_count[name] = {} unless objects_count[name] objects_count[name][v] = (objects_count[name][v] || 0) + 1 end diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index e8f27b3a78..4823273aaa 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -42,11 +42,14 @@ def ontologies_filter @ontologies = submissions_paginate_filter(params) @object_count = count_objects(@ontologies) - update_filters_counts = @object_count.map do |section, values_count| - values_count.map do |value, count| - replace("count_#{section}_#{value}") do - helpers.turbo_frame_tag("count_#{section}_#{value}") do - helpers.content_tag(:span, count.to_s, class: "hide-if-loading #{count.zero? ? 'disabled' : ''}") + if @page.page.eql?(1) + streams = [prepend("ontologies_list_view-page-#{@page.page}", partial: 'ontologies/browser/ontologies')] + streams += @count_objects.map do |section, values_count| + values_count.map do |value, count| + replace("count_#{section}_#{link_last_part(value)}") do + helpers.turbo_frame_tag("count_#{section}_#{link_last_part(value)}") do + helpers.content_tag(:span, count.to_s, class: "hide-if-loading #{count.zero? ? 'disabled' : ''}") + end end end end diff --git a/config/initializers/ontologies_api_client.rb b/config/initializers/ontologies_api_client.rb index fb2d6f94f2..90ec74ab9c 100644 --- a/config/initializers/ontologies_api_client.rb +++ b/config/initializers/ontologies_api_client.rb @@ -7,5 +7,5 @@ config.debug_client = $DEBUG_RUBY_CLIENT || false config.debug_client_keys = $DEBUG_RUBY_CLIENT_KEYS || [] config.apikey = $API_KEY - config.federated_portals = $FEDERATED_PORTALS + config.federated_portals = $FEDERATED_PORTALS || [] end From cb26cb24aaca764a917e54056bbc67f6902737e2 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 10 May 2024 17:28:57 +0200 Subject: [PATCH 14/72] remove binding.pry from final federation code --- app/controllers/application_controller.rb | 2 +- app/controllers/concerns/submission_filter.rb | 2 +- app/helpers/federation_helper.rb | 2 +- config/initializers/ontologies_api_client.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4aa6d0e7b9..d049029f9f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -124,7 +124,7 @@ def domain_ontology_set subdomain = host_parts[0].downcase slices = LinkedData::Client::Models::Slice.all - slices_acronyms = slices.map {|s| s.acronym} rescue binding.pry + slices_acronyms = slices.map {|s| s.acronym} # Set custom ontologies if we're on a subdomain that has them # Else, make sure user ontologies are set appropriately diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index cc261412c2..e61aa2be41 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -67,7 +67,7 @@ def ontologies_filter_url(filters, page: 1, count: false) def filter_using_data(ontologies, query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) submissions = LinkedData::Client::Models::OntologySubmission.all(include: BROWSE_ATTRIBUTES.join(','), also_include_views: true, display_links: false, display_context: false) - submissions = submissions.map { |x| [x[:ontology][:id], x] }.to_h rescue binding.pry + submissions = submissions.map { |x| [x[:ontology][:id], x] }.to_h submissions = ontologies.map { |ont| ontology_hash(ont, submissions) } diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 10d13ec79c..fc80e4a2f9 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -1,7 +1,7 @@ module FederationHelper def federated_portals - $FEDERATED_PORTALS + $FEDERATED_PORTALS || {} end def federated_portal_config(name_key) diff --git a/config/initializers/ontologies_api_client.rb b/config/initializers/ontologies_api_client.rb index 90ec74ab9c..880ea9fea0 100644 --- a/config/initializers/ontologies_api_client.rb +++ b/config/initializers/ontologies_api_client.rb @@ -7,5 +7,5 @@ config.debug_client = $DEBUG_RUBY_CLIENT || false config.debug_client_keys = $DEBUG_RUBY_CLIENT_KEYS || [] config.apikey = $API_KEY - config.federated_portals = $FEDERATED_PORTALS || [] + config.federated_portals = $FEDERATED_PORTALS || {} end From 7879468de0880576f2142deeca500ab9b8a95f75 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Fri, 10 May 2024 18:25:15 +0200 Subject: [PATCH 15/72] add an error message if one of the external portal is down --- Gemfile.lock | 6 +++--- app/controllers/concerns/submission_filter.rb | 4 +++- app/views/ontologies/browser/_ontologies.html.haml | 7 ++++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2964125742..cf4f1ff835 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 0db56d748a1574450ebeb5f67429cd0b766cf932 + revision: 1fcf35adcb8955d6b2d48ed08055d31cb00cd3c2 branch: feature/federate-multiple-apis specs: ontologies_api_client (2.2.0) @@ -174,7 +174,7 @@ GEM flamegraph (0.9.5) globalid (1.2.1) activesupport (>= 6.1) - graphql (2.3.2) + graphql (2.3.3) base64 graphql-client (0.22.0) activesupport (>= 3.0) @@ -349,7 +349,7 @@ GEM omniauth-orcid (2.1.1) omniauth-oauth2 (~> 1.3) ruby_dig (~> 0.0.2) - omniauth-rails_csrf_protection (1.0.1) + omniauth-rails_csrf_protection (1.0.2) actionpack (>= 4.2) omniauth (~> 2.0) open_uri_redirections (0.2.1) diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index e61aa2be41..a581f3b7f6 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -30,6 +30,8 @@ def submissions_paginate_filter(params) @ontologies = LinkedData::Client::Models::Ontology.all(include: 'all', also_include_views: true, display_links: false, display_context: false) + @ontologies, @errors = @ontologies.partition { |x| !x.errors } + # get fair scores of all ontologies @fair_scores = fairness_service_enabled? ? get_fair_score('all') : nil submissions = submissions.reject { |sub| sub.ontology.nil? }.map { |sub| ontology_hash(sub) } @@ -67,7 +69,7 @@ def ontologies_filter_url(filters, page: 1, count: false) def filter_using_data(ontologies, query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) submissions = LinkedData::Client::Models::OntologySubmission.all(include: BROWSE_ATTRIBUTES.join(','), also_include_views: true, display_links: false, display_context: false) - submissions = submissions.map { |x| [x[:ontology][:id], x] }.to_h + submissions = submissions.map { |x| x[:ontology] ? [x[:ontology][:id], x] : nil}.compact.to_h submissions = ontologies.map { |ont| ontology_hash(ont, submissions) } diff --git a/app/views/ontologies/browser/_ontologies.html.haml b/app/views/ontologies/browser/_ontologies.html.haml index fec4643834..06d44d362f 100644 --- a/app/views/ontologies/browser/_ontologies.html.haml +++ b/app/views/ontologies/browser/_ontologies.html.haml @@ -6,7 +6,12 @@ - if @page.page.eql?(1) = content_tag(:p, class: "browse-desc-text", style: "margin-bottom: 12px !important;") do #{t("ontologies.showing_ontologies_size", ontologies_size: @count, analytics_size: @total_ontologies, portals: request_portals_names.join(', ').html_safe).html_safe} (#{sprintf("%.2f", @time)}s) - + - unless @errors.blank? + %div.my-1 + = render Display::AlertComponent.new(type: 'danger') do + - @errors.each do |e| + %div + = e.errors || e - ontologies = c.collection - ontologies.each do |ontology| - config = ontology_portal_config(ontology[:id])&.last || {} From f9f4810e2c5f6e094afa6f4cb7b15cf443c423a0 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 29 Aug 2024 22:54:44 +0200 Subject: [PATCH 16/72] display federated results in search page --- Gemfile | 2 +- Gemfile.lock | 8 ++++++-- app/controllers/concerns/search_aggregator.rb | 8 ++++---- app/controllers/search_controller.rb | 9 +++++++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 77f3217de9..c40d042e5b 100644 --- a/Gemfile +++ b/Gemfile @@ -92,7 +92,7 @@ gem 'will_paginate', '~> 3.0' gem 'inline_svg' gem "iso-639", "~> 0.3.6" gem "flag-icons-rails", "~> 3.4" -gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'development' +gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'feature/federated-search' # Ruby 2.7.8 pinned gems (to remove when migrating to Ruby >= 3.0) gem 'ffi', '~> 1.16.3' diff --git a/Gemfile.lock b/Gemfile.lock index 7765c20fdd..b887db6976 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: abea6c217c0ee93bcfd80c47c2af52747365d491 - branch: development + revision: debf991ef77fe6967ffc5e390fad8e4e7455a326 + branch: feature/federated-search specs: ontologies_api_client (2.2.0) activesupport @@ -12,6 +12,8 @@ GIT lz4-ruby multi_json oj + parallel + request_store spawnling (= 2.1.5) GEM @@ -420,6 +422,8 @@ GEM regexp_parser (2.9.2) reline (0.5.9) io-console (~> 0.5) + request_store (1.7.0) + rack (>= 1.4) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 5656815b1c..90f2ab77f2 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -24,10 +24,11 @@ module SearchAggregator /semanticweb/i ] - def aggregate_results(query, results) + def aggregate_results(query, results, is_federate) 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) + + all_ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym,name', include_views: true, display_links: false, display_context: false, federate: is_federate) grouped_results.map do |group| format_search_result(group, all_ontologies) @@ -59,7 +60,7 @@ def search_concept_label(label) pref_lab.downcase.include?(@search_query.downcase) || @search_query.downcase.include?(pref_lab.downcase) end.first || label.first end - + label end def search_result_elem(class_object, ontology_acronym, title) @@ -229,4 +230,3 @@ def blacklist_cls_id_components(cls_id, blacklist_words) stripped_id end end - diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 841f0f948a..8a04b088ba 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -17,10 +17,15 @@ def index return if @search_query.empty? params[:pagesize] = "150" - results = LinkedData::Client::Models::Class.search(@search_query, params).collection + + is_federate = params[:federate] + + results = LinkedData::Client::Models::Class.search(@search_query, params) + + results = results.collection unless is_federate @advanced_options_open = !search_params_empty? - @search_results = aggregate_results(@search_query, results) + @search_results = aggregate_results(@search_query, results, is_federate) @json_url = json_link("#{rest_url}/search", params.permit!.to_h) end From e40397351f552ccf59da4537648545189eacab11 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 30 Aug 2024 10:57:35 +0200 Subject: [PATCH 17/72] display federated search results in different colors --- .../display/search_result_component.rb | 17 ++++++++++------- .../search_result_component.html.haml | 5 +++-- app/controllers/concerns/search_aggregator.rb | 17 ++++++++++++----- app/helpers/federation_helper.rb | 4 ++++ 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb index 18d56a288e..515485cf25 100644 --- a/app/components/display/search_result_component.rb +++ b/app/components/display/search_result_component.rb @@ -5,7 +5,7 @@ class Display::SearchResultComponent < ViewComponent::Base 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) + def initialize(number: 0,title: nil, ontology_acronym: nil ,uri: nil, definition: nil, link: nil, is_sub_component: false, portal_name: nil, portal_color: nil, portal_light_color: nil) @title = title @uri = uri @definition = definition @@ -13,6 +13,9 @@ def initialize(number: 0,title: nil, ontology_acronym: nil ,uri: nil, definition @is_sub_component = is_sub_component @ontology_acronym = ontology_acronym @number = number.to_s + @portal_name = portal_name + @portal_color = portal_color + @portal_light_color = portal_light_color end def sub_component_class @@ -29,9 +32,9 @@ def reuses_id 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 + content_tag(:div, class: 'button', style: @portal_color ? "background-color: #{@portal_light_color} !important" : '') do concat inline_svg_tag('icons/details.svg') - concat content_tag(:div, class: 'text') { t('search.result_component.details') } + concat content_tag(:div, class: 'text', style: @portal_color ? "color: #{@portal_color} !important" : '') { t('search.result_component.details') } end end end @@ -53,17 +56,17 @@ def mappings_button 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 + content_tag(:div, class: 'button', style: @portal_color ? "background-color: #{@portal_light_color} !important" : '') do concat inline_svg_tag('icons/visualize.svg') - concat content_tag(:div, class: 'text') { t('search.result_component.visualize') } + concat content_tag(:div, class: 'text', style: @portal_color ? "color: #{@portal_color} !important" : '') { t('search.result_component.visualize') } end end end def reveal_ontologies_button(text,id,icon) - content_tag(:div, class: 'button icon-right', 'data-action': "click->reveal-component#toggle", 'data-id': id) do + content_tag(:div, class: 'button icon-right', 'data-action': "click->reveal-component#toggle", 'data-id': id, style: @portal_color ? "background-color: #{@portal_light_color} !important" : '') do inline_svg_tag(icon) + - content_tag(:div, class: 'text') do + content_tag(:div, class: 'text', style: @portal_color ? "color: #{@portal_color} !important" : '') do text end + inline_svg_tag("icons/arrow-down.svg") 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 index 9d4d48bc08..ca9b8c1195 100644 --- a/app/components/display/search_result_component/search_result_component.html.haml +++ b/app/components/display/search_result_component/search_result_component.html.haml @@ -1,5 +1,5 @@ .search-result-component{class: sub_component_class, 'data-controller': 'reveal-component'} - %a.title{href: @link} + %a.title{href: @link, style: @portal_color ? "color: #{@portal_color} !important" : ''} = @title - if @uri .uri @@ -10,7 +10,8 @@ .actions = details_button = visualize_button - = mappings_button + - unless @portal_name + = mappings_button - if subresults? = reveal_ontologies_button("#{subresults.size} #{t('search.result_component.more_from_ontology')}", sub_ontologies_id, 'icons/three-dots.svg') - if reuses? diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 90f2ab77f2..54f92563d4 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -1,5 +1,5 @@ module SearchAggregator - include UrlsHelper, MultiLanguagesHelper + include UrlsHelper, MultiLanguagesHelper, FederationHelper extend ActiveSupport::Concern BLACKLIST_FIX_STR = [ "https://", @@ -64,18 +64,25 @@ def search_concept_label(label) label end def search_result_elem(class_object, ontology_acronym, title) - label = search_concept_label(class_object.prefLabel) - + internal_class_link = "/ontologies/#{ontology_acronym}?p=classes&conceptid=#{escape(class_object.id)}#{helpers.request_lang&.eql?("ALL") ? '' : "&language="+helpers.request_lang.to_s}" + link = is_federation_external_class(class_object) ? class_object.links['ui'] : internal_class_link + portal_name = portal_name_from_uri(class_object.links['ui']) { uri: class_object.id.to_s, title: title.empty? ? label : "#{label} - #{title}", ontology_acronym: ontology_acronym, - link: "/ontologies/#{ontology_acronym}?p=classes&conceptid=#{escape(class_object.id)}#{helpers.request_lang&.eql?("ALL") ? '' : "&language="+helpers.request_lang.to_s}", - definition: class_object.definition + link: link, + definition: class_object.definition, + portal_name: is_federation_external_class(class_object) ? portal_name : nil, + portal_color: is_federation_external_class(class_object) ? federated_portal_color(portal_name) : nil, + portal_light_color: is_federation_external_class(class_object) ? federated_portal_light_color(portal_name) : nil } end + def is_federation_external_class(class_object) + !class_object.links['self'].include?($REST_URL) + end def ontology_name_acronym(ontologies, selected_acronym) ontology = ontologies.select { |x| x.acronym.eql?(selected_acronym.split('/').last) }.first diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index fc80e4a2f9..331787213a 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -23,6 +23,10 @@ def federated_portal_light_color(key) config[:'light-color'] if config end + def portal_name_from_uri(uri) + URI.parse(uri).hostname.split('.').first + end + def ontology_portal_config(id) rest_url = id.split('/')[0..-3].join('/') From 0ad89fa6392ca3467b71c980f7d787054e4901d5 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 6 Sep 2024 07:34:25 +0200 Subject: [PATCH 18/72] add portals param in federated search --- Gemfile.lock | 4 ++-- app/controllers/application_controller.rb | 4 ++++ app/controllers/ontologies_controller.rb | 3 --- app/controllers/search_controller.rb | 6 ++++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b887db6976..9fea52aa10 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,10 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: debf991ef77fe6967ffc5e390fad8e4e7455a326 + revision: 230cb4424a296106f918399f58813aafd5306270 branch: feature/federated-search specs: ontologies_api_client (2.2.0) - activesupport + activesupport (~> 7.0.3) excon faraday faraday-excon (~> 2.0.0) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index dac1c04204..2e38e99c28 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -468,6 +468,10 @@ def json_link(url, optional_params) optional_params_str = filtered_params.map { |param, value| "#{param}=#{value}" }.join("&") return base_url + optional_params_str + "&apikey=#{$API_KEY}" end + + def set_federated_portals + RequestStore.store[:federated_portals] = params[:portals]&.split(',') + end private def not_found_record(exception) diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index 437b5df9eb..4982d367d7 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -559,7 +559,4 @@ def determine_layout end end - def set_federated_portals - RequestStore.store[:federated_portals] = params[:portals]&.split(',') - end end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 8a04b088ba..7ac15b8a18 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -18,12 +18,14 @@ def index params[:pagesize] = "150" - is_federate = params[:federate] + set_federated_portals + + is_federate = params[:federate] || params[:portals] results = LinkedData::Client::Models::Class.search(@search_query, params) results = results.collection unless is_federate - + @advanced_options_open = !search_params_empty? @search_results = aggregate_results(@search_query, results, is_federate) @json_url = json_link("#{rest_url}/search", params.permit!.to_h) From 5db66028117a451945789bf567522dc1062a8bfc Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 6 Sep 2024 13:46:51 +0200 Subject: [PATCH 19/72] add federation checks in search page --- Gemfile.lock | 2 +- app/components/display/search_result_component.rb | 10 ++++++++++ .../search_result_component.html.haml | 13 +++++++++---- app/controllers/search_controller.rb | 5 +++-- app/views/search/index.html.haml | 7 ++++++- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9fea52aa10..6b6b5a7256 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 230cb4424a296106f918399f58813aafd5306270 + revision: 4cff0f52f6a9cc539bfa129c1e989dfc02403541 branch: feature/federated-search specs: ontologies_api_client (2.2.0) diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb index 515485cf25..1f003d7ac2 100644 --- a/app/components/display/search_result_component.rb +++ b/app/components/display/search_result_component.rb @@ -72,4 +72,14 @@ def reveal_ontologies_button(text,id,icon) inline_svg_tag("icons/arrow-down.svg") end end + + def portal_button(name: nil , color: nil , light_color: nil) + content_tag(:div, class: 'button icon-right', style: color ? "background-color: #{light_color} !important" : '') do + inline_svg_tag('logos/ontoportal.svg') + + content_tag(:div, class: 'text', style: color ? "color: #{color} !important" : '') do + name.humanize + end + end + + end end 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 index ca9b8c1195..c701099011 100644 --- a/app/components/display/search_result_component/search_result_component.html.haml +++ b/app/components/display/search_result_component/search_result_component.html.haml @@ -1,6 +1,9 @@ .search-result-component{class: sub_component_class, 'data-controller': 'reveal-component'} - %a.title{href: @link, style: @portal_color ? "color: #{@portal_color} !important" : ''} - = @title + %a.title{href: @link, style: @portal_color ? "color: #{@portal_color} !important" : '', target: @portal_color ? "_blank" : ''} + .d-flex.align-items-center + = @title + = inline_svg_tag 'icons/external-link.svg', class: "ml-1 #{@portal_color ? '' : 'd-none'}" + - if @uri .uri = @uri @@ -8,14 +11,16 @@ = display_in_multiple_languages(@definition) .actions - = details_button - = visualize_button - unless @portal_name + = details_button + = visualize_button = mappings_button - if subresults? = reveal_ontologies_button("#{subresults.size} #{t('search.result_component.more_from_ontology')}", sub_ontologies_id, 'icons/three-dots.svg') - if reuses? = reveal_ontologies_button("#{t('search.result_component.reuses_in')} #{reuses.size} ontologies", reuses_id, 'icons/reuses.svg') + - if @portal_name + = portal_button(name: @portal_name, color: @portal_color, light_color: @portal_light_color) - if subresults? .more-from-ontology.d-none{id: sub_ontologies_id} .vertical-line diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 7ac15b8a18..fbd096be62 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -13,6 +13,7 @@ def index @advanced_options_open = false @search_results = [] @json_url = json_link("#{rest_url}/search", {}) + params[:portals] = params[:portals]&.join(',') return if @search_query.empty? @@ -25,7 +26,7 @@ def index results = LinkedData::Client::Models::Class.search(@search_query, params) results = results.collection unless is_federate - + @advanced_options_open = !search_params_empty? @search_results = aggregate_results(@search_query, results, is_federate) @json_url = json_link("#{rest_url}/search", params.permit!.to_h) @@ -148,7 +149,7 @@ def search_params [ :ontologies, :categories, :also_search_properties, :also_search_obsolete, :also_search_views, - :require_exact_match, :require_definition + :require_exact_match, :require_definition, :portals ] end diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index 28ecbf2eed..e11944d80b 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -15,7 +15,12 @@ = t("search.advanced_options.ontologies") .field = ontologies_selector(id:'search_page_ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(',')) - + .filter-container + .title + Results from external portals + .field.d-flex + - federated_portals.each do |key, config| + = group_chip_component(name: "portals[]", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') .right .filter-container .title From e68d651475a916a69684c2493b54787ced531247 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Mon, 9 Sep 2024 15:57:54 +0200 Subject: [PATCH 20/72] merge federated search results --- app/components/display/search_result_component.rb | 3 ++- .../search_result_component.html.haml | 3 ++- app/controllers/concerns/search_aggregator.rb | 15 ++++++++++++++- app/controllers/search_controller.rb | 2 ++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb index 1f003d7ac2..990db4d5e8 100644 --- a/app/components/display/search_result_component.rb +++ b/app/components/display/search_result_component.rb @@ -5,7 +5,7 @@ class Display::SearchResultComponent < ViewComponent::Base 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, portal_name: nil, portal_color: nil, portal_light_color: nil) + def initialize(number: 0,title: nil, ontology_acronym: nil ,uri: nil, definition: nil, link: nil, is_sub_component: false, portal_name: nil, portal_color: nil, portal_light_color: nil, other_portals: []) @title = title @uri = uri @definition = definition @@ -16,6 +16,7 @@ def initialize(number: 0,title: nil, ontology_acronym: nil ,uri: nil, definition @portal_name = portal_name @portal_color = portal_color @portal_light_color = portal_light_color + @other_portals = other_portals end def sub_component_class 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 index c701099011..765fb24013 100644 --- a/app/components/display/search_result_component/search_result_component.html.haml +++ b/app/components/display/search_result_component/search_result_component.html.haml @@ -8,7 +8,6 @@ .uri = @uri - if @definition - = display_in_multiple_languages(@definition) .actions - unless @portal_name @@ -21,6 +20,8 @@ = reveal_ontologies_button("#{t('search.result_component.reuses_in')} #{reuses.size} ontologies", reuses_id, 'icons/reuses.svg') - if @portal_name = portal_button(name: @portal_name, color: @portal_color, light_color: @portal_light_color) + - @other_portals.each do |p| + = portal_button(name: p[:name], color: p[:color], light_color: p[:light_color]) - if subresults? .more-from-ontology.d-none{id: sub_ontologies_id} .vertical-line diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 54f92563d4..f00c785361 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -30,9 +30,22 @@ def aggregate_results(query, results, is_federate) all_ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym,name', include_views: true, display_links: false, display_context: false, federate: is_federate) - grouped_results.map do |group| + search_results = grouped_results.map do |group| format_search_result(group, all_ontologies) end + + search_results.each do |element| + element[:root][:other_portals] = [] + element[:reuses].reject! do |reuse| + if element[:root][:title] == reuse[:root][:title] + portal_name = reuse[:root][:portal_name] + element[:root][:other_portals] << {name: portal_name, color: federated_portal_color(portal_name), light_color: federated_portal_light_color(portal_name)} + true + else + false + end + end + end end def format_search_result(result, ontologies) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index fbd096be62..11344aaf10 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -29,6 +29,8 @@ def index @advanced_options_open = !search_params_empty? @search_results = aggregate_results(@search_query, results, is_federate) + + @json_url = json_link("#{rest_url}/search", params.permit!.to_h) end From 065570bedb2834694bbd25d33de90b84f251eeae Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Mon, 9 Sep 2024 16:12:32 +0200 Subject: [PATCH 21/72] show portals names in federated search result chips in the form 'AgroPortal' instead of 'agroportal' --- app/components/display/search_result_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb index 990db4d5e8..cb67545d30 100644 --- a/app/components/display/search_result_component.rb +++ b/app/components/display/search_result_component.rb @@ -78,7 +78,7 @@ def portal_button(name: nil , color: nil , light_color: nil) content_tag(:div, class: 'button icon-right', style: color ? "background-color: #{light_color} !important" : '') do inline_svg_tag('logos/ontoportal.svg') + content_tag(:div, class: 'text', style: color ? "color: #{color} !important" : '') do - name.humanize + name.humanize.gsub("portal", "Portal") end end From 2a33a0de48b39ebbcf40c8c50c6f72781273a84d Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Mon, 9 Sep 2024 17:33:41 +0200 Subject: [PATCH 22/72] sort federated search results by string similarity --- Gemfile | 3 ++ Gemfile.lock | 2 + app/controllers/concerns/search_aggregator.rb | 38 +++++++++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Gemfile b/Gemfile index c40d042e5b..ae7f2bece0 100644 --- a/Gemfile +++ b/Gemfile @@ -88,6 +88,9 @@ gem 'view_component', '~> 2.72' # Pagination library for Rails gem 'will_paginate', '~> 3.0' +# String similarity +gem 'string-similarity' + # Render SVG files in Rails views gem 'inline_svg' gem "iso-639", "~> 0.3.6" diff --git a/Gemfile.lock b/Gemfile.lock index 6b6b5a7256..d58dbd3d4b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -511,6 +511,7 @@ GEM net-ssh (>= 2.8.0) stimulus-rails (1.3.4) railties (>= 6.0.0) + string-similarity (2.1.0) stringio (3.1.1) strscan (3.1.0) temple (0.10.3) @@ -621,6 +622,7 @@ DEPENDENCIES simplecov-cobertura sprockets-rails stimulus-rails + string-similarity terser turbo-rails tzinfo-data diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index f00c785361..7faebfd424 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -1,5 +1,6 @@ module SearchAggregator include UrlsHelper, MultiLanguagesHelper, FederationHelper + require 'string-similarity' extend ActiveSupport::Concern BLACKLIST_FIX_STR = [ "https://", @@ -34,18 +35,11 @@ def aggregate_results(query, results, is_federate) format_search_result(group, all_ontologies) end - search_results.each do |element| - element[:root][:other_portals] = [] - element[:reuses].reject! do |reuse| - if element[:root][:title] == reuse[:root][:title] - portal_name = reuse[:root][:portal_name] - element[:root][:other_portals] << {name: portal_name, color: federated_portal_color(portal_name), light_color: federated_portal_light_color(portal_name)} - true - else - false - end - end + if is_federate + search_results = merge_federated_results(search_results) + search_results = sort_results_by_string_similarity(query, search_results) end + search_results end def format_search_result(result, ontologies) @@ -249,4 +243,26 @@ def blacklist_cls_id_components(cls_id, blacklist_words) stripped_id end + + def merge_federated_results(search_results) + search_results.each do |element| + element[:root][:other_portals] = [] + element[:reuses].reject! do |reuse| + if element[:root][:title] == reuse[:root][:title] + portal_name = reuse[:root][:portal_name] + element[:root][:other_portals] << {name: portal_name, color: federated_portal_color(portal_name), light_color: federated_portal_light_color(portal_name)} + true + else + false + end + end + end + end + + def sort_results_by_string_similarity(query, search_results) + search_results = search_results.sort_by do |entry| + root_similarity = String::Similarity.cosine(query, entry[:root][:title].split('-').first.gsub(" ", "").downcase) + -root_similarity + end + end end From 62e1cc0c88990e147f18df7332cc024266b4e60e Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 12 Sep 2024 10:27:20 +0200 Subject: [PATCH 23/72] fix merge development to federated search issues --- Gemfile.lock | 2 +- app/helpers/federation_helper.rb | 4 +++ config/bioportal_config_env.rb.sample | 44 +++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 79da59cb25..9da7889567 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 4cff0f52f6a9cc539bfa129c1e989dfc02403541 + revision: 929920411203d25c57e3fd5e264200764181ca3e branch: feature/federated-search specs: ontologies_api_client (2.2.0) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index afd60dfa00..d4470c44c4 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -21,6 +21,10 @@ def federated_portal_config(name_key) federated_portals[name_key.to_sym] end + def portal_name_from_uri(uri) + URI.parse(uri).hostname.split('.').first + end + def federated_portal_name(key) config = federated_portal_config(key) config ? config[:name] : key diff --git a/config/bioportal_config_env.rb.sample b/config/bioportal_config_env.rb.sample index 352805b196..7ea55f59d0 100644 --- a/config/bioportal_config_env.rb.sample +++ b/config/bioportal_config_env.rb.sample @@ -213,6 +213,50 @@ $HOME_PAGE_LOGOS = [ # Federation configuration +$FEDERATED_PORTALS = { + bioportal: { + name: "BioPortal", + api: 'https://data.bioontology.org/', + ui: 'https://bioportal.bioontology.org/', + apikey: '8b5b7825-538d-40e0-9e9e-5ab9274a9aeb', + color: '#234979', + 'light-color': '#E9F2FA', + }, + agroportal: { + name: "AgroPortal", + api: 'https://data.agroportal.lirmm.fr', + ui: 'https://agroportal.lirmm.fr', + apikey: '1de0a270-29c5-4dda-b043-7c3580628cd5', + color: '#349696', + 'light-color': '#F1F6FA', + }, + ecoportal: { + name: "EcoPortal", + api: 'https://data.ecoportal.lifewatch.eu/', + ui: 'https://ecoportal.lifewatch.eu', + apikey: "43a437ba-a437-4bf0-affd-ab520e584719", + color: '#2076C9', + 'light-color': '#E9F2FA', + }, + # earthportal: { + # name: 'EarthPortal', + # api: 'https://earthportal.eu:8443/', + # ui: 'https://earthportal.eu', + # apikey: "c9147279-954f-41bd-b068-da9b0c441288", + # color: '#404696', + # 'light-color': '#404696' + # }, + biodivportal: { + name: "BioDivPortal", + api: 'https://data.biodivportal.gfbio.org/', + ui: 'https://biodivportal.gfbio.org/', + apikey: "47a57aa3-7b54-4f34-b695-dbb5f5b7363e", + color: '#349696', + 'light-color': '#EBF5F5', + } +} + + $PORTALS_INSTANCES = [ { name: 'AgroPortal', From 07f0e44cffee8dd699740e62516888599761e2cd Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 12 Sep 2024 10:42:34 +0200 Subject: [PATCH 24/72] clean search result component --- app/components/display/search_result_component.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb index cb67545d30..74f0e725b2 100644 --- a/app/components/display/search_result_component.rb +++ b/app/components/display/search_result_component.rb @@ -33,9 +33,9 @@ def reuses_id 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', style: @portal_color ? "background-color: #{@portal_light_color} !important" : '') do + content_tag(:div, class: 'button') do concat inline_svg_tag('icons/details.svg') - concat content_tag(:div, class: 'text', style: @portal_color ? "color: #{@portal_color} !important" : '') { t('search.result_component.details') } + concat content_tag(:div, class: 'text') { t('search.result_component.details') } end end end @@ -57,9 +57,9 @@ def mappings_button 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', style: @portal_color ? "background-color: #{@portal_light_color} !important" : '') do + content_tag(:div, class: 'button') do concat inline_svg_tag('icons/visualize.svg') - concat content_tag(:div, class: 'text', style: @portal_color ? "color: #{@portal_color} !important" : '') { t('search.result_component.visualize') } + concat content_tag(:div, class: 'text') { t('search.result_component.visualize') } end end end From 2e5dd7b82c5983a14da16abc9ee00e719647c7a6 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 12 Sep 2024 10:57:23 +0200 Subject: [PATCH 25/72] internationalize: results from other portals in search and browse page --- app/views/ontologies/browser/browse.html.haml | 2 +- app/views/search/index.html.haml | 2 +- config/locales/en.yml | 5 ++++- config/locales/fr.yml | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index fd9777e3a8..e0695e73d8 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -72,7 +72,7 @@ %span.show-if-loading = render LoaderComponent.new(small:true) %div{data:{controller: "show-filter-count browse-filters", action: "change->show-filter-count#updateCount change->browse-filters#dispatchFilterEvent"}, id: "portals_filter_container"} - = render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: "Results from external portals") do + = render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: t('federation.results_from_external_portals')) do .browse-filter-checks-container.px-1 - federated_portals.each do |key, config| = group_chip_component(name: "portals", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index e11944d80b..b8895ced4a 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -17,7 +17,7 @@ = ontologies_selector(id:'search_page_ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(',')) .filter-container .title - Results from external portals + = t('federation.results_from_external_portals') .field.d-flex - federated_portals.each do |key, config| = group_chip_component(name: "portals[]", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') diff --git a/config/locales/en.yml b/config/locales/en.yml index 79ae1583e5..fb54a1cef2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1496,4 +1496,7 @@ en: taxonomy: groups_and_categories: Groups and Categories description: In AgroPortal, ontologies are organized in groups and tagged with categories. Typically, groups associate ontologies from the same project or organization for better identification of the provenance. Whereas categories are about subjects/topics and enable to classify ontologies. As of 2016, AgroPortal's categories were established in cooperation with FAO AIMS. In 2024, we moved to UNESCO nomenclature for fields of science and technology. Groups and categories, along with other metadata, can be used on the “Browse” page of AgroPortal to filter out the list of ontologies. - show_sub_categories: Show sub categories \ No newline at end of file + show_sub_categories: Show sub categories + + federation: + results_from_external_portals: Results from external portals \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index f044708cb8..34c68bafa8 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1535,4 +1535,6 @@ fr: taxonomy: groups_and_categories: Groupes et Catégories description: Dans AgroPortal, les ontologies sont organisées en groupes et étiquetées avec des catégories. Typiquement, les groupes associent des ontologies provenant du même projet ou de la même organisation pour une meilleure identification de la provenance. Tandis que les catégories concernent des sujets/thématiques et permettent de classifier les ontologies. En 2016, les catégories d'AgroPortal ont été établies en coopération avec FAO AIMS. En 2024, nous sommes passés à la nomenclature de l'UNESCO pour les domaines des sciences et des technologies. Les groupes et les catégories, ainsi que d'autres métadonnées, peuvent être utilisés sur la page “Parcourir” d'AgroPortal pour filtrer la liste des ontologies. - show_sub_categories: Afficher les sous-catégories \ No newline at end of file + show_sub_categories: Afficher les sous-catégories + federation: + results_from_external_portals: Résultats provenant de portails externes From d1b228a60064180cbd56b29fdc3b6084fde504e3 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 12 Sep 2024 21:52:14 +0200 Subject: [PATCH 26/72] fix performance issue in federated search --- Gemfile.lock | 2 +- app/controllers/concerns/search_aggregator.rb | 2 +- config/bioportal_config_env.rb.sample | 22 +++++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9da7889567..79da59cb25 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 929920411203d25c57e3fd5e264200764181ca3e + revision: 4cff0f52f6a9cc539bfa129c1e989dfc02403541 branch: feature/federated-search specs: ontologies_api_client (2.2.0) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 7eefdd5a3d..4e2ac084bc 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -29,7 +29,7 @@ def aggregate_results(query, results, is_federate) 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, federate: is_federate) + all_ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym,name', include_views: true, display_links: false, display_context: false) search_results = grouped_results.map do |group| format_search_result(group, all_ontologies) diff --git a/config/bioportal_config_env.rb.sample b/config/bioportal_config_env.rb.sample index 7ea55f59d0..9036b0e875 100644 --- a/config/bioportal_config_env.rb.sample +++ b/config/bioportal_config_env.rb.sample @@ -262,7 +262,7 @@ $PORTALS_INSTANCES = [ name: 'AgroPortal', api: 'https://data.agroportal.lirmm.fr', ui: 'https://agroportal.lirmm.fr/', - color: '#349696', + color: '#3CB371', apikey: '1de0a270-29c5-4dda-b043-7c3580628cd5', 'light-color': '#F1F6FA', }, @@ -277,14 +277,17 @@ $PORTALS_INSTANCES = [ { name: 'SIFR BioPortal', ui: 'https://bioportal.lirmm.fr/', + api: 'https://data.bioportal.lirmm.fr/', + apikey: '1de0a270-29c5-4dda-b043-7c3580628cd5', color: '#74a9cb', + 'light-color': '#E9F2FA', }, { name: 'EcoPortal', ui: 'https://ecoportal.lifewatch.eu/', api: 'https://data.ecoportal.lifewatch.eu/', apikey: "43a437ba-a437-4bf0-affd-ab520e584719", - color: '#0d508a', + color: '#2076C9', 'light-color': '#E9F2FA', }, { @@ -300,26 +303,37 @@ $PORTALS_INSTANCES = [ { name: 'IndustryPortal', ui: 'http://industryportal.enit.fr', + api: 'https://data.industryportal.enit.fr/', + apikey: '019adb70-1d64-41b7-8f6e-8f7e5eb54942', color: '#1c0f5d', + 'light-color': '#F0F5F6', }, { name: 'EarthPortal', ui: 'https://earthportal.eu/', api: 'https://data.earthportal.eu/', apikey: "c9147279-954f-41bd-b068-da9b0c441288", - color: '#1e2251', + color: '#404696', 'light-color': '#F0F5F6' }, + { + name: 'TestPortal', + ui: 'https://testportal.lirmm.fr/', + api: 'https://data.testportal.lirmm.fr/', + color: '#74a9cb', + apikey: '1de0a270-29c5-4dda-b043-7c3580628cd5', + }, { name: 'BiodivPortal', ui: 'https://biodivportal.gfbio.org/', api: 'https://data.biodivportal.gfbio.org/', apikey: "47a57aa3-7b54-4f34-b695-dbb5f5b7363e", - color: '#33691B', + color: '#349696', 'light-color': '#EBF5F5', } ] + $ONTOPORTAL_WEBSITE_LINK = "https://ontoportal.org/" $ONTOPORTAL_GITHUB_REPO = "https://github.com/ontoportal" From bb96959cc197966493a3b3346df1f07ed8bb5a92 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 13 Sep 2024 10:06:15 +0200 Subject: [PATCH 27/72] merge results using class id and ontology acronym --- app/controllers/concerns/search_aggregator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 4e2ac084bc..3626cf0fd9 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -248,7 +248,7 @@ def merge_federated_results(search_results) search_results.each do |element| element[:root][:other_portals] = [] element[:reuses].reject! do |reuse| - if element[:root][:title] == reuse[:root][:title] + if (element[:root][:ontology_acronym] == reuse[:root][:ontology_acronym]) && (element[:root][:uri] == reuse[:root][:uri]) portal_name = reuse[:root][:portal_name] element[:root][:other_portals] << {name: portal_name, color: federated_portal_color(portal_name), light_color: federated_portal_light_color(portal_name)} true From edb6937c1a52632523ca7d6427e5589d9c063c74 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 13 Sep 2024 10:11:18 +0200 Subject: [PATCH 28/72] extract federation enabled into a helper in federated search --- app/controllers/concerns/search_aggregator.rb | 4 ++-- app/controllers/search_controller.rb | 6 ++---- app/helpers/federation_helper.rb | 4 ++++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 3626cf0fd9..6a849dad1e 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -25,7 +25,7 @@ module SearchAggregator /semanticweb/i ] - def aggregate_results(query, results, is_federate) + def aggregate_results(query, results) ontologies = aggregate_by_ontology(results) grouped_results = add_subordinate_ontologies(query, ontologies) @@ -35,7 +35,7 @@ def aggregate_results(query, results, is_federate) format_search_result(group, all_ontologies) end - if is_federate + if federatation_enabled? search_results = merge_federated_results(search_results) search_results = sort_results_by_string_similarity(query, search_results) end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 11344aaf10..ba4a2f104f 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -21,14 +21,12 @@ def index set_federated_portals - is_federate = params[:federate] || params[:portals] - results = LinkedData::Client::Models::Class.search(@search_query, params) - results = results.collection unless is_federate + results = results.collection unless federatation_enabled? @advanced_options_open = !search_params_empty? - @search_results = aggregate_results(@search_query, results, is_federate) + @search_results = aggregate_results(@search_query, results) @json_url = json_link("#{rest_url}/search", params.permit!.to_h) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index d4470c44c4..6950ba3ae2 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -98,4 +98,8 @@ def request_portals_names content_tag(:span, federated_portal_name(name), style: color ? "color: #{color}" : "", class: color ? "" : "text-primary") end.compact end + + def federatation_enabled? + params[:federate] || params[:portals] + end end From d986607888a7ba33d5cd65a74fc8d71b2424b18d Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 13 Sep 2024 10:21:08 +0200 Subject: [PATCH 29/72] refactor search result elem function --- app/controllers/concerns/search_aggregator.rb | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 6a849dad1e..359c1f8f48 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -72,18 +72,22 @@ def search_concept_label(label) end def search_result_elem(class_object, ontology_acronym, title) label = search_concept_label(class_object.prefLabel) - internal_class_link = "/ontologies/#{ontology_acronym}?p=classes&conceptid=#{escape(class_object.id)}#{helpers.request_lang&.eql?("ALL") ? '' : "&language="+helpers.request_lang.to_s}" - link = is_federation_external_class(class_object) ? class_object.links['ui'] : internal_class_link - portal_name = portal_name_from_uri(class_object.links['ui']) + request_lang = helpers.request_lang&.eql?("ALL") ? '' : "&language=#{helpers.request_lang}" + internal_class_link = "/ontologies/#{ontology_acronym}?p=classes&conceptid=#{escape(class_object.id)}#{request_lang}" + + is_external = is_federation_external_class(class_object) + link = is_external ? class_object.links['ui'] : internal_class_link + portal_name = is_external ? portal_name_from_uri(class_object.links['ui']) : nil + { uri: class_object.id.to_s, - title: title.nil? || title.empty? ? "#{label} - #{ontology_acronym}" : "#{label} - #{title}", + title: title.to_s.empty? ? "#{label} - #{ontology_acronym}" : "#{label} - #{title}", ontology_acronym: ontology_acronym, link: link, - definition: class_object.definition, - portal_name: is_federation_external_class(class_object) ? portal_name : nil, - portal_color: is_federation_external_class(class_object) ? federated_portal_color(portal_name) : nil, - portal_light_color: is_federation_external_class(class_object) ? federated_portal_light_color(portal_name) : nil + definition: class_object.definition, + portal_name: portal_name, + portal_color: is_external ? federated_portal_color(portal_name) : nil, + portal_light_color: is_external ? federated_portal_light_color(portal_name) : nil } end From d285722bf886089b779e01a02c3bdcc8bd1cf45f Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 13 Sep 2024 10:26:10 +0200 Subject: [PATCH 30/72] add realtime benchmark for search federated search --- app/controllers/search_controller.rb | 13 +++++-------- app/views/search/index.html.haml | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index ba4a2f104f..df014dd6ea 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -20,15 +20,12 @@ def index params[:pagesize] = "150" set_federated_portals - - results = LinkedData::Client::Models::Class.search(@search_query, params) - - results = results.collection unless federatation_enabled? - + @time = Benchmark.realtime do + results = LinkedData::Client::Models::Class.search(@search_query, params) + results = results.collection unless federatation_enabled? + @search_results = aggregate_results(@search_query, results) + end @advanced_options_open = !search_params_empty? - @search_results = aggregate_results(@search_query, results) - - @json_url = json_link("#{rest_url}/search", params.permit!.to_h) end diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index b8895ced4a..fdfeccd99e 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -40,7 +40,7 @@ .search-page-options{class: @search_results.empty? ? 'justify-content-end': ''} - unless @search_results.empty? .search-page-number-of-results - = "#{t('search.match_in')} #{@search_results.length} #{t('search.ontologies')}" + = "#{t('search.match_in')} #{@search_results.length} #{t('search.ontologies')} (#{sprintf("%.2f", @time)}s)" %div.d-flex .search-page-json.mx-4.mt-1 = search_json_link From 4d0f827160850dfc2675eba6a70c95942381a3a2 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 13 Sep 2024 11:08:36 +0200 Subject: [PATCH 31/72] add portal names and colors in top of federated search results --- app/views/search/index.html.haml | 2 +- config/locales/en.yml | 3 ++- config/locales/fr.yml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index fdfeccd99e..8702df407a 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -40,7 +40,7 @@ .search-page-options{class: @search_results.empty? ? 'justify-content-end': ''} - unless @search_results.empty? .search-page-number-of-results - = "#{t('search.match_in')} #{@search_results.length} #{t('search.ontologies')} (#{sprintf("%.2f", @time)}s)" + = "#{t('search.match_in')} #{@search_results.length} #{t('search.ontologies')} #{t('federation.from')} #{request_portals_names.join(', ').html_safe} (#{sprintf("%.2f", @time)}s)".html_safe %div.d-flex .search-page-json.mx-4.mt-1 = search_json_link diff --git a/config/locales/en.yml b/config/locales/en.yml index fb54a1cef2..2c994a3d37 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1499,4 +1499,5 @@ en: show_sub_categories: Show sub categories federation: - results_from_external_portals: Results from external portals \ No newline at end of file + results_from_external_portals: Results from external portals + from: from \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 34c68bafa8..2a47f018a6 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1538,3 +1538,4 @@ fr: show_sub_categories: Afficher les sous-catégories federation: results_from_external_portals: Résultats provenant de portails externes + from: de From 6f716856332fb12d0e4eb82cf25aa9d92ef8129c Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 13 Sep 2024 20:39:01 +0200 Subject: [PATCH 32/72] make sort by string similarity not case sensitive --- app/controllers/concerns/search_aggregator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 359c1f8f48..7961740c9b 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -265,7 +265,7 @@ def merge_federated_results(search_results) def sort_results_by_string_similarity(query, search_results) search_results = search_results.sort_by do |entry| - root_similarity = String::Similarity.cosine(query, entry[:root][:title].split('-').first.gsub(" ", "").downcase) + root_similarity = String::Similarity.cosine(query.downcase, entry[:root][:title].split('-').first.gsub(" ", "").downcase) -root_similarity end end From fc8ec7e9e6909616daf91965f15b7f7a33f7001b Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 20 Sep 2024 12:58:04 +0200 Subject: [PATCH 33/72] remove duplicated federation configuration --- config/bioportal_config_env.rb.sample | 46 --------------------------- 1 file changed, 46 deletions(-) diff --git a/config/bioportal_config_env.rb.sample b/config/bioportal_config_env.rb.sample index 9036b0e875..f3c50f6b39 100644 --- a/config/bioportal_config_env.rb.sample +++ b/config/bioportal_config_env.rb.sample @@ -211,52 +211,6 @@ $HOME_PAGE_LOGOS = [ } ] - -# Federation configuration -$FEDERATED_PORTALS = { - bioportal: { - name: "BioPortal", - api: 'https://data.bioontology.org/', - ui: 'https://bioportal.bioontology.org/', - apikey: '8b5b7825-538d-40e0-9e9e-5ab9274a9aeb', - color: '#234979', - 'light-color': '#E9F2FA', - }, - agroportal: { - name: "AgroPortal", - api: 'https://data.agroportal.lirmm.fr', - ui: 'https://agroportal.lirmm.fr', - apikey: '1de0a270-29c5-4dda-b043-7c3580628cd5', - color: '#349696', - 'light-color': '#F1F6FA', - }, - ecoportal: { - name: "EcoPortal", - api: 'https://data.ecoportal.lifewatch.eu/', - ui: 'https://ecoportal.lifewatch.eu', - apikey: "43a437ba-a437-4bf0-affd-ab520e584719", - color: '#2076C9', - 'light-color': '#E9F2FA', - }, - # earthportal: { - # name: 'EarthPortal', - # api: 'https://earthportal.eu:8443/', - # ui: 'https://earthportal.eu', - # apikey: "c9147279-954f-41bd-b068-da9b0c441288", - # color: '#404696', - # 'light-color': '#404696' - # }, - biodivportal: { - name: "BioDivPortal", - api: 'https://data.biodivportal.gfbio.org/', - ui: 'https://biodivportal.gfbio.org/', - apikey: "47a57aa3-7b54-4f34-b695-dbb5f5b7363e", - color: '#349696', - 'light-color': '#EBF5F5', - } -} - - $PORTALS_INSTANCES = [ { name: 'AgroPortal', From f7a4e47cea483cf30a772265f2e1ebd14e267081 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 20 Sep 2024 13:24:13 +0200 Subject: [PATCH 34/72] extract portal button in home page to federation helper, fix the style of it and make it clickable --- Gemfile.lock | 3 ++- app/assets/stylesheets/components/search_result.scss | 1 + app/components/display/search_result_component.rb | 11 +---------- .../search_result_component.html.haml | 4 ++-- app/controllers/concerns/search_aggregator.rb | 3 ++- app/helpers/federation_helper.rb | 9 +++++++++ 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 45a8917875..4cc820671e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -555,6 +555,7 @@ GEM zeitwerk (2.6.18) PLATFORMS + x86_64-linux x86_64-linux-musl DEPENDENCIES @@ -630,4 +631,4 @@ DEPENDENCIES will_paginate (~> 3.0) BUNDLED WITH - 2.4.22 + 2.3.23 diff --git a/app/assets/stylesheets/components/search_result.scss b/app/assets/stylesheets/components/search_result.scss index fc99288618..494b66fe75 100644 --- a/app/assets/stylesheets/components/search_result.scss +++ b/app/assets/stylesheets/components/search_result.scss @@ -52,6 +52,7 @@ } .search-result-component .actions .button svg{ width: 16px; + height: 16px; } .search-result-component .actions .button svg path{ diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb index 74f0e725b2..3896bc94f3 100644 --- a/app/components/display/search_result_component.rb +++ b/app/components/display/search_result_component.rb @@ -2,6 +2,7 @@ class Display::SearchResultComponent < ViewComponent::Base include UrlsHelper include ModalHelper include MultiLanguagesHelper + include FederationHelper renders_many :subresults, Display::SearchResultComponent renders_many :reuses, Display::SearchResultComponent @@ -73,14 +74,4 @@ def reveal_ontologies_button(text,id,icon) inline_svg_tag("icons/arrow-down.svg") end end - - def portal_button(name: nil , color: nil , light_color: nil) - content_tag(:div, class: 'button icon-right', style: color ? "background-color: #{light_color} !important" : '') do - inline_svg_tag('logos/ontoportal.svg') + - content_tag(:div, class: 'text', style: color ? "color: #{color} !important" : '') do - name.humanize.gsub("portal", "Portal") - end - end - - end end 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 index 765fb24013..80f7274673 100644 --- a/app/components/display/search_result_component/search_result_component.html.haml +++ b/app/components/display/search_result_component/search_result_component.html.haml @@ -19,9 +19,9 @@ - if reuses? = reveal_ontologies_button("#{t('search.result_component.reuses_in')} #{reuses.size} ontologies", reuses_id, 'icons/reuses.svg') - if @portal_name - = portal_button(name: @portal_name, color: @portal_color, light_color: @portal_light_color) + = portal_button(name: @portal_name, color: @portal_color, light_color: @portal_light_color, link: @link) - @other_portals.each do |p| - = portal_button(name: p[:name], color: p[:color], light_color: p[:light_color]) + = portal_button(name: p[:name], color: p[:color], light_color: p[:light_color], link: p[:link]) - if subresults? .more-from-ontology.d-none{id: sub_ontologies_id} .vertical-line diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 7961740c9b..a14e2bf088 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -254,7 +254,8 @@ def merge_federated_results(search_results) element[:reuses].reject! do |reuse| if (element[:root][:ontology_acronym] == reuse[:root][:ontology_acronym]) && (element[:root][:uri] == reuse[:root][:uri]) portal_name = reuse[:root][:portal_name] - element[:root][:other_portals] << {name: portal_name, color: federated_portal_color(portal_name), light_color: federated_portal_light_color(portal_name)} + link = reuse[:root][:link] + element[:root][:other_portals] << {name: portal_name, color: federated_portal_color(portal_name), light_color: federated_portal_light_color(portal_name), link: link} true else false diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 6950ba3ae2..58c4e2aea7 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -102,4 +102,13 @@ def request_portals_names def federatation_enabled? params[:federate] || params[:portals] end + + def portal_button(name: nil , color: nil , light_color: nil, link: nil, tooltip: nil) + content_tag(:a, href: link, target: '_blank', 'data-controller': 'tooltip', title: tooltip, class: 'button icon-right', style: color ? "background-color: #{light_color} !important" : '') do + inline_svg_tag('logos/ontoportal.svg') + + content_tag(:div, class: 'text', style: color ? "color: #{color} !important" : '') do + name.humanize.gsub("portal", "Portal") + end + end + end end From f8742b6109975bdcc8c70aecefdb110d077a2367 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Mon, 23 Sep 2024 13:42:31 +0200 Subject: [PATCH 35/72] fix icons colors in federation --- .../display/search_result_component.rb | 4 ++-- .../search_result_component.html.haml | 4 ++-- app/helpers/federation_helper.rb | 2 +- .../controllers/federation_controller.js | 18 ++++++++++++++++++ app/javascript/controllers/index.js | 5 ++++- 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 app/javascript/controllers/federation_controller.js diff --git a/app/components/display/search_result_component.rb b/app/components/display/search_result_component.rb index 3896bc94f3..e8da14a353 100644 --- a/app/components/display/search_result_component.rb +++ b/app/components/display/search_result_component.rb @@ -67,11 +67,11 @@ def visualize_button def reveal_ontologies_button(text,id,icon) content_tag(:div, class: 'button icon-right', 'data-action': "click->reveal-component#toggle", 'data-id': id, style: @portal_color ? "background-color: #{@portal_light_color} !important" : '') do - inline_svg_tag(icon) + + inline_svg_tag(icon, class: "federated-icon-#{@portal_name}") + content_tag(:div, class: 'text', style: @portal_color ? "color: #{@portal_color} !important" : '') do text end + - inline_svg_tag("icons/arrow-down.svg") + inline_svg_tag("icons/arrow-down.svg", class: "federated-icon-#{@portal_name}") end end end 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 index 80f7274673..446ada6408 100644 --- a/app/components/display/search_result_component/search_result_component.html.haml +++ b/app/components/display/search_result_component/search_result_component.html.haml @@ -1,8 +1,8 @@ -.search-result-component{class: sub_component_class, 'data-controller': 'reveal-component'} +.search-result-component{class: sub_component_class, 'data-controller': 'reveal-component federation', 'data-federation-config-value': federated_portals.to_h.to_json} %a.title{href: @link, style: @portal_color ? "color: #{@portal_color} !important" : '', target: @portal_color ? "_blank" : ''} .d-flex.align-items-center = @title - = inline_svg_tag 'icons/external-link.svg', class: "ml-1 #{@portal_color ? '' : 'd-none'}" + = inline_svg_tag 'icons/external-link.svg', class: "ml-1 federated-icon-#{@portal_name} #{@portal_color ? '' : 'd-none'}" - if @uri .uri diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 58c4e2aea7..d1b027d296 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -105,7 +105,7 @@ def federatation_enabled? def portal_button(name: nil , color: nil , light_color: nil, link: nil, tooltip: nil) content_tag(:a, href: link, target: '_blank', 'data-controller': 'tooltip', title: tooltip, class: 'button icon-right', style: color ? "background-color: #{light_color} !important" : '') do - inline_svg_tag('logos/ontoportal.svg') + + inline_svg_tag('logos/ontoportal.svg', class: "federated-icon-#{name}") + content_tag(:div, class: 'text', style: color ? "color: #{color} !important" : '') do name.humanize.gsub("portal", "Portal") end diff --git a/app/javascript/controllers/federation_controller.js b/app/javascript/controllers/federation_controller.js new file mode 100644 index 0000000000..31f104feb3 --- /dev/null +++ b/app/javascript/controllers/federation_controller.js @@ -0,0 +1,18 @@ +import { Controller } from "@hotwired/stimulus" + +// Connects to data-controller="federation" +export default class extends Controller { + static values = { + config: Object, + } + connect() { + const style = document.createElement('style'); + let styles = ''; + Object.entries(this.configValue).forEach(([key, portal]) => { + styles += `.federated-icon-${key} path { fill: ${portal.color} !important; }\n`; + }); + style.innerHTML = styles; + document.head.appendChild(style); + } + } + \ No newline at end of file diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 24f39bf453..68d74a48a5 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -105,4 +105,7 @@ import MappingsController from "./mappings_visualization_controller" application.register('mappings', MappingsController) import ConceptsJsonButtonController from "./concepts_json_button_controller.js" -application.register('concepts-json', ConceptsJsonButtonController) \ No newline at end of file +application.register('concepts-json', ConceptsJsonButtonController) + +import FederationController from "./federation_controller" +application.register('federation', FederationController) \ No newline at end of file From 52e9b27ffd876fe8754d933d034c89bccbfbd10e Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 27 Sep 2024 10:39:44 +0200 Subject: [PATCH 36/72] display a message in the federated search results when a portal is not responding --- Gemfile.lock | 4 ++-- app/controllers/search_controller.rb | 9 ++++++++- app/helpers/federation_helper.rb | 5 +++++ app/views/search/index.html.haml | 2 ++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4cc820671e..bb422e95fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 4cff0f52f6a9cc539bfa129c1e989dfc02403541 + revision: 929920411203d25c57e3fd5e264200764181ca3e branch: feature/federated-search specs: ontologies_api_client (2.2.0) @@ -631,4 +631,4 @@ DEPENDENCIES will_paginate (~> 3.0) BUNDLED WITH - 2.3.23 + 2.4.22 diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index df014dd6ea..b14aded69b 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -22,7 +22,14 @@ def index set_federated_portals @time = Benchmark.realtime do results = LinkedData::Client::Models::Class.search(@search_query, params) - results = results.collection unless federatation_enabled? + if federatation_enabled? + @federation_errors = results[:errors].map{|e| find_portal_name_by_api(e.split(' ').last.gsub('search', ''))} + @federation_errors = @federation_errors.map{ |p| "#{p} is not responding. " }.join(' ') + + results = results[:results] + else + results = results.collection + end @search_results = aggregate_results(@search_query, results) end @advanced_options_open = !search_params_empty? diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index d1b027d296..536592a994 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -111,4 +111,9 @@ def portal_button(name: nil , color: nil , light_color: nil, link: nil, tooltip: end end end + + def find_portal_name_by_api(api_url) + portal = federated_portals.values.find { |portal| portal[:api] == api_url } + portal ? portal[:name] : nil + end end diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index 8702df407a..39be354543 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -54,6 +54,8 @@ =inline_svg_tag 'icons/hide.svg' .text = t('search.hide_advanced_options') + - unless @federation_errors.blank? + = render Display::AlertComponent.new(message: @federation_errors, type: "warning") - if @search_results .search-page-results-container - number = 0 From bf5fe76436249256ce0cdf65f0edf3f7f3cbdf69 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 27 Sep 2024 14:01:22 +0200 Subject: [PATCH 37/72] disable input chips in federated search page for the portals that are not responding --- .../controllers/federation_controller.js | 38 ++++++++++++++++--- app/views/search/index.html.haml | 4 +- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/app/javascript/controllers/federation_controller.js b/app/javascript/controllers/federation_controller.js index 31f104feb3..9a072819e4 100644 --- a/app/javascript/controllers/federation_controller.js +++ b/app/javascript/controllers/federation_controller.js @@ -5,14 +5,40 @@ export default class extends Controller { static values = { config: Object, } + static targets = ['chips'] connect() { - const style = document.createElement('style'); - let styles = ''; - Object.entries(this.configValue).forEach(([key, portal]) => { - styles += `.federated-icon-${key} path { fill: ${portal.color} !important; }\n`; + this.#initIconsStyle() + this.#initInputChips() + } + #initIconsStyle(){ + const style = document.createElement('style'); + let styles = ''; + Object.entries(this.configValue).forEach(([key, portal]) => { + styles += `.federated-icon-${key} path { fill: ${portal.color} !important; }\n`; + }); + style.innerHTML = styles; + document.head.appendChild(style); + } + + #initInputChips(){ + for (const key in this.configValue) { + $.ajax({ + url: this.configValue[key].api, + type: 'GET', + success: function(response) { + console.log('working'); + }, + error: function() { + let chipsInputs = this.chipsTarget.querySelectorAll('input') + Array.from(chipsInputs).forEach(input => { + if (input.value === this.configValue[key].name.toLowerCase()) { + input.disabled = true; + input.parentNode.style.opacity = '0.5' + } + }); + }.bind(this) }); - style.innerHTML = styles; - document.head.appendChild(style); + } } } \ No newline at end of file diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index 39be354543..795db68522 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -15,10 +15,10 @@ = t("search.advanced_options.ontologies") .field = ontologies_selector(id:'search_page_ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(',')) - .filter-container + .filter-container{'data-controller': 'federation', 'data-federation-config-value': federated_portals.to_h.to_json} .title = t('federation.results_from_external_portals') - .field.d-flex + .field.d-flex{'data-federation-target': 'chips'} - federated_portals.each do |key, config| = group_chip_component(name: "portals[]", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') .right From 3366fca5cc6cf1d193680fd6dd1001e63a191bc3 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 27 Sep 2024 14:49:15 +0200 Subject: [PATCH 38/72] show tooltip for disabled input chips in the federated search --- app/javascript/controllers/federation_controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/controllers/federation_controller.js b/app/javascript/controllers/federation_controller.js index 9a072819e4..e88e0a1680 100644 --- a/app/javascript/controllers/federation_controller.js +++ b/app/javascript/controllers/federation_controller.js @@ -33,7 +33,9 @@ export default class extends Controller { Array.from(chipsInputs).forEach(input => { if (input.value === this.configValue[key].name.toLowerCase()) { input.disabled = true; - input.parentNode.style.opacity = '0.5' + input.parentNode.style.opacity = '0.5'; + input.parentNode.setAttribute('data-controller', 'tooltip'); + input.parentNode.setAttribute('title', `${this.configValue[key].name} is currently down`); } }); }.bind(this) From d2177f9194d876bba2d372759ae4c6d7add94bf1 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Tue, 1 Oct 2024 15:35:02 +0200 Subject: [PATCH 39/72] clean search controller federation code --- Gemfile.lock | 2 +- app/controllers/search_controller.rb | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bb422e95fa..46607df0af 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 929920411203d25c57e3fd5e264200764181ca3e + revision: f6b39981b3dda17e911c497762681d15210a4ad2 branch: feature/federated-search specs: ontologies_api_client (2.2.0) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index b14aded69b..222e2a3c0e 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -22,14 +22,9 @@ def index set_federated_portals @time = Benchmark.realtime do results = LinkedData::Client::Models::Class.search(@search_query, params) - if federatation_enabled? - @federation_errors = results[:errors].map{|e| find_portal_name_by_api(e.split(' ').last.gsub('search', ''))} - @federation_errors = @federation_errors.map{ |p| "#{p} is not responding. " }.join(' ') - - results = results[:results] - else - results = results.collection - end + @federation_errors = results[:errors].map{|e| find_portal_name_by_api(e.split(' ').last.gsub('search', ''))} + @federation_errors = @federation_errors.map{ |p| "#{p} is not responding. " }.join(' ') + results = results[:results] @search_results = aggregate_results(@search_query, results) end @advanced_options_open = !search_params_empty? From 5c7d43496a805452b456d7a26e121759f629b3fb Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Tue, 1 Oct 2024 22:52:36 +0200 Subject: [PATCH 40/72] update ontologies api ruby client to the latest version of federated search --- Gemfile.lock | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 46607df0af..cf29740310 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,10 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: f6b39981b3dda17e911c497762681d15210a4ad2 + revision: d2134bf8738c82c5bbf7971d7295873f8c6d953a branch: feature/federated-search specs: ontologies_api_client (2.2.0) - activesupport (~> 7.0.3) + activesupport (~> 7.0.4) excon faraday faraday-excon (~> 2.0.0) @@ -150,14 +150,14 @@ GEM debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) - deepl-rb (2.5.3) + deepl-rb (3.0.1) diff-lcs (1.5.1) docile (1.4.1) domain_name (0.6.20240107) ed25519 (1.3.0) erubi (1.13.0) erubis (2.7.0) - excon (0.111.0) + excon (0.112.0) execjs (2.9.1) faraday (2.0.1) faraday-net_http (~> 2.0) @@ -176,7 +176,7 @@ GEM sass-rails globalid (1.2.1) activesupport (>= 6.1) - graphql (2.3.14) + graphql (2.3.16) base64 fiber-storage graphql-client (0.23.0) @@ -203,7 +203,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.7) domain_name (~> 0.5) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) i18n-tasks (0.9.37) activesupport (>= 4.0.2) @@ -225,7 +225,7 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.7.2) - irb (1.14.0) + irb (1.14.1) rdoc (>= 4.0.0) reline (>= 0.4.2) iso-639 (0.3.6) @@ -245,7 +245,7 @@ GEM bindata faraday (~> 2.0) faraday-follow_redirects - jwt (2.8.2) + jwt (2.9.1) base64 language_server-protocol (3.17.0.3) launchy (3.0.1) @@ -302,7 +302,7 @@ GEM time net-http (0.3.2) uri - net-imap (0.4.15) + net-imap (0.4.16) date net-protocol net-pop (0.1.2) @@ -317,7 +317,7 @@ GEM net-protocol net-ssh (7.2.3) netrc (0.11.0) - newrelic_rpm (9.13.0) + newrelic_rpm (9.14.0) nio4r (2.7.3) nokogiri (1.15.6-x86_64-linux) racc (~> 1.4) @@ -328,7 +328,7 @@ GEM rack (>= 1.2, < 4) snaky_hash (~> 2.0) version_gem (~> 1.1) - oj (3.16.5) + oj (3.16.6) bigdecimal (>= 3.0) ostruct (>= 0.2) omniauth (2.1.2) @@ -338,8 +338,8 @@ GEM omniauth-github (2.0.1) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) - omniauth-google-oauth2 (1.1.3) - jwt (>= 2.0) + omniauth-google-oauth2 (1.2.0) + jwt (>= 2.9) oauth2 (~> 2.0) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) @@ -369,7 +369,7 @@ GEM psych (5.1.2) stringio public_suffix (5.1.1) - puma (5.6.8) + puma (5.6.9) nio4r (~> 2.0) racc (1.8.1) rack (2.2.9) @@ -429,8 +429,8 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.3.7) - rouge (4.3.0) + rexml (3.3.8) + rouge (4.4.0) rspec-core (3.13.1) rspec-support (~> 3.13.0) rspec-expectations (3.13.3) @@ -458,7 +458,7 @@ GEM rubocop-ast (>= 1.32.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.2) + rubocop-ast (1.32.3) parser (>= 3.3.1.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) @@ -522,13 +522,12 @@ GEM time (0.4.0) date timeout (0.4.1) - turbo-rails (2.0.6) + turbo-rails (2.0.10) actionpack (>= 6.0.0) - activejob (>= 6.0.0) railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) + unicode-display_width (2.6.0) uri (0.13.1) version_gem (1.1.4) view_component (2.83.0) @@ -540,7 +539,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.23.1) + webmock (3.24.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) From cb8276d8d8d0143e7f023b3e1960bd12fcdfebff Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 2 Oct 2024 10:46:51 +0200 Subject: [PATCH 41/72] fix federated browse icons colors --- .../ontology_browse_card_component.html.haml | 3 ++- app/views/ontologies/browser/browse.html.haml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml index 6a08ccc836..be22043169 100644 --- a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml +++ b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml @@ -7,7 +7,8 @@ = ontology[:name]+" ("+ontology[:acronym]+")" = private_ontology_icon(ontology[:private]) - if external_ontology? - = render Display::InfoTooltipComponent.new(text: "Federated ontology from #{ontology[:sources].map{|x| link_to(x,x)}.join(', ')}", icon: 'external-link.svg') + %div{class: "federated-icon-#{@portal_name&.downcase} d-inline"} + = render Display::InfoTooltipComponent.new(text: "Federated ontology from #{ontology[:sources].map{|x| link_to(x,x)}.join(', ')}", icon: 'external-link.svg') - if session[:user]&.admin? - ontology_status = status_string(ontology) = render Display::InfoTooltipComponent.new(text: ontology_status, icon: submission_status_icons(ontology_status)) diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index e0695e73d8..b0abb8cf94 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -1,5 +1,5 @@ .browse-center - .browse-container + .browse-container{'data-controller': 'federation', 'data-federation-config-value': federated_portals.to_h.to_json} .container.align-alert - if session[:user]&.admin? %div{style:'width: 70%;'} From d320de2b14c4607969d65a78d24786b3ae4a5ca3 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 2 Oct 2024 10:47:11 +0200 Subject: [PATCH 42/72] add tooltip for portal button in federated search --- .../search_result_component/search_result_component.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 446ada6408..0693686deb 100644 --- a/app/components/display/search_result_component/search_result_component.html.haml +++ b/app/components/display/search_result_component/search_result_component.html.haml @@ -19,9 +19,9 @@ - if reuses? = reveal_ontologies_button("#{t('search.result_component.reuses_in')} #{reuses.size} ontologies", reuses_id, 'icons/reuses.svg') - if @portal_name - = portal_button(name: @portal_name, color: @portal_color, light_color: @portal_light_color, link: @link) + = portal_button(name: @portal_name, color: @portal_color, light_color: @portal_light_color, link: @link, tooltip: "Source #{@portal_name.humanize.gsub("portal", "Portal")}") - @other_portals.each do |p| - = portal_button(name: p[:name], color: p[:color], light_color: p[:light_color], link: p[:link]) + = portal_button(name: p[:name], color: p[:color], light_color: p[:light_color], link: p[:link], tooltip: "Source #{p[:name].humanize.gsub("portal", "Portal")}") - if subresults? .more-from-ontology.d-none{id: sub_ontologies_id} .vertical-line From 02ce7cb4d631a03995be80fb80886b352cd88796 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 2 Oct 2024 11:04:13 +0200 Subject: [PATCH 43/72] open external ontologies links in new tab in federated browse --- .../ontology_browse_card_component.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml index be22043169..75d8a43c02 100644 --- a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml +++ b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml @@ -3,7 +3,7 @@ .d-flex .browse-ontology-description .browse-ontology-title-bar - %a.browse-ontology-title{:href => onto_link, data: {'turbo': 'false'} , style: style_text} + %a.browse-ontology-title{:href => onto_link, data: {'turbo': 'false'} , style: style_text, target: external_ontology? ? '_blank' : ''} = ontology[:name]+" ("+ontology[:acronym]+")" = private_ontology_icon(ontology[:private]) - if external_ontology? From cd83029f8f9d8025bf8785f3c50353276eddbf37 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 2 Oct 2024 17:29:36 +0200 Subject: [PATCH 44/72] use portal button helper in federated browse --- Gemfile.lock | 11 ++++--- .../stylesheets/application.css.scss.erb | 1 + .../stylesheets/components/search_result.scss | 2 +- app/assets/stylesheets/federation.scss | 31 +++++++++++++++++++ .../ontology_browse_card_component.html.haml | 10 ++---- app/helpers/federation_helper.rb | 4 +-- 6 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 app/assets/stylesheets/federation.scss diff --git a/Gemfile.lock b/Gemfile.lock index cf29740310..0dd52ad941 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -150,7 +150,7 @@ GEM debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) - deepl-rb (3.0.1) + deepl-rb (3.0.2) diff-lcs (1.5.1) docile (1.4.1) domain_name (0.6.20240107) @@ -287,9 +287,10 @@ GEM marcel (1.0.4) matrix (0.4.2) method_source (1.1.0) - mime-types (3.5.2) + mime-types (3.6.0) + logger mime-types-data (~> 3.2015) - mime-types-data (3.2024.0903) + mime-types-data (3.2024.1001) mini_mime (1.1.5) minitest (5.25.1) msgpack (1.7.2) @@ -315,7 +316,7 @@ GEM net-ssh (>= 5.0.0, < 8.0.0) net-smtp (0.5.0) net-protocol - net-ssh (7.2.3) + net-ssh (7.3.0) netrc (0.11.0) newrelic_rpm (9.14.0) nio4r (2.7.3) @@ -436,7 +437,7 @@ GEM rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-rails (7.0.1) diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/stylesheets/application.css.scss.erb index c5b6723fb3..52b88a0a6d 100755 --- a/app/assets/stylesheets/application.css.scss.erb +++ b/app/assets/stylesheets/application.css.scss.erb @@ -60,6 +60,7 @@ @import "tools"; @import "portal_configuration"; @import "taxonomy"; +@import "federation"; /* Bootstrap and Font Awesome */ @import "bootstrap"; diff --git a/app/assets/stylesheets/components/search_result.scss b/app/assets/stylesheets/components/search_result.scss index 494b66fe75..f5d8510458 100644 --- a/app/assets/stylesheets/components/search_result.scss +++ b/app/assets/stylesheets/components/search_result.scss @@ -41,7 +41,7 @@ display: flex; justify-content: center; align-items: center; - border-radius: 4px; + border-radius: 5px; background-color: var(--light-color); padding: 5px 13px; margin-right: 10px; diff --git a/app/assets/stylesheets/federation.scss b/app/assets/stylesheets/federation.scss new file mode 100644 index 0000000000..6bb893e251 --- /dev/null +++ b/app/assets/stylesheets/federation.scss @@ -0,0 +1,31 @@ +.federation-portal-button{ + display: flex; + justify-content: center; + align-items: center; + border-radius: 5px; + background-color: var(--light-color); + padding: 5px 13px; + margin-right: 0px; + height: 100%; +} + + +.federation-portal-button:hover{ + cursor: pointer; +} + +.federation-portal-button svg{ + width: 16px; + height: 16px; +} +.federation-portal-button svg path{ + fill: var(--primary-color); +} + +.federation-portal-button .text{ + color: var(--primary-color); + margin-left: 8px; +} +.federation-portal-button.icon-right .text{ + margin:0 8px; +} \ No newline at end of file diff --git a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml index 75d8a43c02..6b6791710c 100644 --- a/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml +++ b/app/components/ontology_browse_card_component/ontology_browse_card_component.html.haml @@ -78,14 +78,8 @@ - ontology[:sources].each do |id| - config = ontology_portal_config(id)&.last || internal_portal_config(id) || {} - unless config.blank? - %div.mx-1{title: content_tag(:div, "Source #{config[:name]}"), data:{controller: 'tooltip', 'tooltip-interactive-value': 'true'}} - = render ChipButtonComponent.new(type: "clickable" , style: style_bg) do - = link_to ontoportal_ui_link(id), target: '_top' do - %span.d-inline - %span.mr-1 - = inline_svg 'logo-white.svg', width: "20", height: "20" - %span - = config[:name] + %span{style: "padding: 3px 0; margin-right: 0.25rem;"} + = portal_button(name: config[:name], color: @text_color, light_color: @bg_light_color, link: ontoportal_ui_link(id), tooltip: "Source #{config[:name]}") - if session[:user]&.admin? %div.mx-1{title: content_tag(:div, debug(ontology), style: 'height: 300px; overflow: scroll'), data:{controller: 'tooltip', 'tooltip-interactive-value': 'true'}} diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 536592a994..2d852ae9ba 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -104,8 +104,8 @@ def federatation_enabled? end def portal_button(name: nil , color: nil , light_color: nil, link: nil, tooltip: nil) - content_tag(:a, href: link, target: '_blank', 'data-controller': 'tooltip', title: tooltip, class: 'button icon-right', style: color ? "background-color: #{light_color} !important" : '') do - inline_svg_tag('logos/ontoportal.svg', class: "federated-icon-#{name}") + + content_tag(:a, href: link, target: '_blank', 'data-controller': 'tooltip', title: tooltip, class: 'federation-portal-button button icon-right', style: color ? "background-color: #{light_color} !important" : '') do + inline_svg_tag('logos/ontoportal.svg', class: "federated-icon-#{name.downcase}") + content_tag(:div, class: 'text', style: color ? "color: #{color} !important" : '') do name.humanize.gsub("portal", "Portal") end From aacfd566cc735dcc434a25e7a46e64a098cebff0 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 3 Oct 2024 18:00:49 +0200 Subject: [PATCH 45/72] cache federation status call --- app/helpers/federation_helper.rb | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 2d852ae9ba..7412989fa5 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -116,4 +116,41 @@ def find_portal_name_by_api(api_url) portal = federated_portals.values.find { |portal| portal[:api] == api_url } portal ? portal[:name] : nil end + + def federation_portals_status + federation_apis = federated_portals.map { |p| [ p.first , p.last[:api] ] } + + tmp_portals_up = Rails.cache.read('federation_portals_up') + if tmp_portals_up + return tmp_portals_up + else + portals_up = [] + federation_apis.each do |portal| + begin + conn = Faraday.new(url: portal.last) do |f| + f.request :url_encoded + f.adapter Faraday.default_adapter + f.options.timeout = 20 + f.options.open_timeout = 20 + end + response = conn.get + if response.success? + parsed_response = JSON.parse(response.body) + # todo: check if the response is a correct response + portals_up << portal.first + else + raise "API call failed with status #{response.status}" + end + + rescue => e + # timout or error + end + Rails.cache.write('federation_portals_up', portals_up, expires_in: 2.hours) + end + + return portals_up + + end + + end end From d17c31b9b1b47cc4284817220225defab622dea5 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 3 Oct 2024 18:38:44 +0200 Subject: [PATCH 46/72] update chip helpers to support disabled state --- app/assets/stylesheets/components/chips.scss | 2 +- app/components/chips_component.rb | 5 +++-- app/helpers/components_helper.rb | 20 ++++++++++---------- app/helpers/inputs_helper.rb | 6 +++--- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/assets/stylesheets/components/chips.scss b/app/assets/stylesheets/components/chips.scss index ac1a2a939f..e120ced177 100644 --- a/app/assets/stylesheets/components/chips.scss +++ b/app/assets/stylesheets/components/chips.scss @@ -32,7 +32,7 @@ } .chips-container.disabled div label > span, .chips-container div label span:has(span.disabled){ - background-color: #f8f9fa !important; + opacity: 60%; } .chips-container div label input[type="checkbox"]:checked ~ span{ diff --git a/app/components/chips_component.rb b/app/components/chips_component.rb index 624681f812..b97ddb3bbc 100644 --- a/app/components/chips_component.rb +++ b/app/components/chips_component.rb @@ -1,16 +1,17 @@ class ChipsComponent < ViewComponent::Base renders_one :count - def initialize(id:nil, name:, label: nil, value: nil, checked: false, tooltip: nil) + def initialize(id:nil, name:, label: nil, value: nil, checked: false, tooltip: nil, disabled: false) @id = id || name @name = name @value = value || 'true' @checked = checked @label = label || @value @tooltip = tooltip + @disabled = disabled end def checked? @checked end -end \ No newline at end of file +end diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb index 210e4ab57c..1cb32afc1d 100644 --- a/app/helpers/components_helper.rb +++ b/app/helpers/components_helper.rb @@ -1,19 +1,19 @@ module ComponentsHelper include TermsReuses - def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, &block) + def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, disabled: false, &block) content_tag(:div, data: { controller: 'tooltip' }, title: tooltip) do - check_input(id: id, name: name, value: value, label: label, checked: checked, &block) + check_input(id: id, name: name, value: value, label: label, checked: checked, disabled: disabled, &block) end end - def group_chip_component(id: nil, name: , object: , checked: , value: nil, title: nil, &block) + def group_chip_component(id: nil, name: , object: , checked: , value: nil, title: nil, disabled: false, &block) title ||= object["name"] value ||= (object["value"] || object["acronym"] || object["id"]) chips_component(id: id || value, name: name, label: object["acronym"], checked: checked, - value: value, tooltip: title, &block) + value: value, tooltip: title, disabled: disabled, &block) end alias :category_chip_component :group_chip_component @@ -29,7 +29,7 @@ def check_resolvability_container(url) end end end - + def search_page_input_component(name:, value: nil, placeholder: , button_icon: 'icons/search.svg', type: 'text', &block) content_tag :div, class: 'search-page-input-container', data: { controller: 'reveal' } do search_input = content_tag :div, class: 'search-page-input' do @@ -63,7 +63,7 @@ def paginated_list_component(id:, results:, next_page_url:, child_url:, child_tu end end) end - + concepts = c.collection if concepts && !concepts.empty? concepts.each do |concept| @@ -104,7 +104,7 @@ def copy_link_to_clipboard(url, show_content: false) end - def generated_link_to_clipboard(url, acronym) + def generated_link_to_clipboard(url, acronym) url = "#{$UI_URL}/ontologies/#{acronym}/#{link_last_part(url)}" content_tag(:span, id: "generate_portal_link", style: 'display: inline-block;') do render ClipboardComponent.new(icon: 'icons/copy_link.svg', title: "#{t("components.copy_portal_uri", portal_name: portal_name)} #{link_to(url)}", message: url, show_content: false) @@ -123,7 +123,7 @@ def htaccess_tag(acronym) def link_to_with_actions(link_to_tag, acronym: nil, url: nil, copy: true, check_resolvability: true, generate_link: true, generate_htaccess: false) tag = link_to_tag url = link_to_tag if url.nil? - + tag += content_tag(:span, class: 'mx-1') do concat copy_link_to_clipboard(url) if copy concat generated_link_to_clipboard(url, acronym) if generate_link @@ -136,11 +136,11 @@ def link_to_with_actions(link_to_tag, acronym: nil, url: nil, copy: true, check_ def tree_component(root, selected, target_frame:, sub_tree: false, id: nil, auto_click: false, submission: nil, &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, t('components.error_block') end diff --git a/app/helpers/inputs_helper.rb b/app/helpers/inputs_helper.rb index cd4782b6f3..242de76990 100644 --- a/app/helpers/inputs_helper.rb +++ b/app/helpers/inputs_helper.rb @@ -28,8 +28,8 @@ def number_input(name: , label: '', value: ) value: value) end - def check_input(id:, name:, value:, label: '', checked: false, &block) - render ChipsComponent.new(name: name, id: id, label: label, value: value, checked: checked) do |c| + def check_input(id:, name:, value:, label: '', checked: false, disabled: false, &block) + render ChipsComponent.new(name: name, id: id, label: label, value: value, checked: checked, disabled: disabled) do |c| if block_given? capture(c, &block) end @@ -82,4 +82,4 @@ def attribute_error(attr) def input_error_message(name) attribute_error(method_name(name)) end -end \ No newline at end of file +end From 7ca635953fdd177d2fbf1b84d76a7a4a3c1ab2e0 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 3 Oct 2024 18:39:25 +0200 Subject: [PATCH 47/72] use turbo frame for federation input chips, and use federation portals status to disable non working portals --- app/controllers/home_controller.rb | 7 ++++++ .../controllers/federation_controller.js | 23 ------------------- .../home/federation_portals_status.html.haml | 6 +++++ app/views/search/index.html.haml | 10 ++++---- config/routes.rb | 1 + 5 files changed, 19 insertions(+), 28 deletions(-) create mode 100644 app/views/home/federation_portals_status.html.haml diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index d7e4beeb31..c0856acbbb 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -166,6 +166,13 @@ def annotator_recommender_form end end + def federation_portals_status + @acronym = params[:acronym] + @key = params[:key] + @checked = params[:checked].eql?('true') + render 'home/federation_portals_status' + end + private # Dr. Musen wants 5 specific groups to appear first, sorted by order of importance. diff --git a/app/javascript/controllers/federation_controller.js b/app/javascript/controllers/federation_controller.js index e88e0a1680..7551f3d04b 100644 --- a/app/javascript/controllers/federation_controller.js +++ b/app/javascript/controllers/federation_controller.js @@ -8,7 +8,6 @@ export default class extends Controller { static targets = ['chips'] connect() { this.#initIconsStyle() - this.#initInputChips() } #initIconsStyle(){ const style = document.createElement('style'); @@ -20,27 +19,5 @@ export default class extends Controller { document.head.appendChild(style); } - #initInputChips(){ - for (const key in this.configValue) { - $.ajax({ - url: this.configValue[key].api, - type: 'GET', - success: function(response) { - console.log('working'); - }, - error: function() { - let chipsInputs = this.chipsTarget.querySelectorAll('input') - Array.from(chipsInputs).forEach(input => { - if (input.value === this.configValue[key].name.toLowerCase()) { - input.disabled = true; - input.parentNode.style.opacity = '0.5'; - input.parentNode.setAttribute('data-controller', 'tooltip'); - input.parentNode.setAttribute('title', `${this.configValue[key].name} is currently down`); - } - }); - }.bind(this) - }); - } - } } \ No newline at end of file diff --git a/app/views/home/federation_portals_status.html.haml b/app/views/home/federation_portals_status.html.haml new file mode 100644 index 0000000000..972dc987fe --- /dev/null +++ b/app/views/home/federation_portals_status.html.haml @@ -0,0 +1,6 @@ +- portal_up = federation_portals_status.include?(@key.to_sym) += render TurboFrameComponent.new(id:"federation_portals_status_#{@key}") do |container| + %div{style: "cursor: default;"} + = group_chip_component(name: "portals[]", object: { "acronym" => @acronym, "value" => @key }, checked: @checked, title: "#{!portal_up ? "#{@key.humanize.gsub('portal', 'Portal')} is currently down" : ''}", disabled: !portal_up) + +-# TODO put the portal is down tooltip message in local lang files \ No newline at end of file diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index 795db68522..3649f2934b 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -1,4 +1,4 @@ -.search-page-container +.search-page-container{data: {turbo_frame: 'federation_portals_status'}} .search-page-subcontainer{'data-controller': 'reveal-component'} = form_tag(search_path, method: :get,data:{turbo: true, controller: 'form-url', action: 'submit->form-url#submit'}) do = search_page_input_component(placeholder: t('search.search_place_holder'), name: "q", value: @search_query) do @@ -15,12 +15,13 @@ = t("search.advanced_options.ontologies") .field = ontologies_selector(id:'search_page_ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(',')) - .filter-container{'data-controller': 'federation', 'data-federation-config-value': federated_portals.to_h.to_json} + .filter-container .title = t('federation.results_from_external_portals') - .field.d-flex{'data-federation-target': 'chips'} + .field.d-flex - federated_portals.each do |key, config| - = group_chip_component(name: "portals[]", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') + = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") + .right .filter-container .title @@ -76,4 +77,3 @@ .browse-empty-illustration %img{:src => "#{asset_path("empty-box.svg")}"} %p No result was found - \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index b28c26ec02..226b6d0ca5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -131,6 +131,7 @@ end get '' => 'home#index' + get 'home/federation_portals_status', to: 'home#federation_portals_status' match 'sparql_proxy', to: 'admin#sparql_endpoint', via: [:get, :post] From aa7913c68eeab01581bc81a60db72e7a3e9c98f2 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 3 Oct 2024 19:14:28 +0200 Subject: [PATCH 48/72] add skelton loading animation for federation input chips --- app/assets/stylesheets/components/chips.scss | 38 +++++++++++++++++++- app/helpers/application_helper.rb | 12 ++++++- app/views/search/index.html.haml | 5 +-- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/components/chips.scss b/app/assets/stylesheets/components/chips.scss index e120ced177..770a41a3f9 100644 --- a/app/assets/stylesheets/components/chips.scss +++ b/app/assets/stylesheets/components/chips.scss @@ -42,4 +42,40 @@ .chips-container div label input[type="checkbox"]:checked ~ span .chips-check-icon{ display:unset; -} \ No newline at end of file +} + +.chips-container.loading div { + cursor: default; + opacity: 0.6; +} + +.chips-container .skeleton { + width: 80px; + height: 36px; + background-color: #e0e0e0; + border-radius: 5px; + animation: shimmer 4s infinite; + position: relative; + overflow: hidden; +} + +@keyframes shimmer { + 0% { + background-position: -200px 0; + } + 100% { + background-position: 200px 0; + } +} + +.chips-container .skeleton::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0.2) 100%); + animation: shimmer 4s infinite; + border-radius: 5px; +} diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0f0a6f0c9d..ffc71d919b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -154,7 +154,7 @@ def link_last_part(url) def at_slice? !@subdomain_filter.nil? && !@subdomain_filter[:active].nil? && @subdomain_filter[:active] == true end - + def add_comment_button(parent_id, parent_type) if session[:user].nil? link_to t('application.add_comment'), login_index_path(redirect: request.url), class: "secondary-button regular-button slim" @@ -547,4 +547,14 @@ def categories_select(id: nil, name: nil, selected: 'None') categories_for_select = LinkedData::Client::Models::Category.all.map{|x| ["#{x.name} (#{x.acronym})", x.id]}.unshift(["None", '']) render Input::SelectComponent.new(id: id, name: name, value: categories_for_select, selected: selected) end + + def chips_skelton + content_tag(:div, class: 'chips-container loading') do + content_tag(:div) do + content_tag(:label) do + content_tag(:span, '', class: 'skeleton') + end + end + end + end end diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index 3649f2934b..eb8f5e4736 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -20,8 +20,9 @@ = t('federation.results_from_external_portals') .field.d-flex - federated_portals.each do |key, config| - = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") - + = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| + - container.loader do + = chips_skelton .right .filter-container .title From bf8224fe1d18209943d7dfa0d7627914821f9db6 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 4 Oct 2024 10:16:16 +0200 Subject: [PATCH 49/72] cache federation input chips separately --- app/controllers/home_controller.rb | 3 +- app/helpers/federation_helper.rb | 52 +++++++++---------- .../home/federation_portals_status.html.haml | 3 +- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index c0856acbbb..b1b2d6e9d3 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -4,7 +4,7 @@ class HomeController < ApplicationController layout :determine_layout - include FairScoreHelper + include FairScoreHelper, FederationHelper def index @analytics = helpers.ontologies_analytics @@ -170,6 +170,7 @@ def federation_portals_status @acronym = params[:acronym] @key = params[:key] @checked = params[:checked].eql?('true') + @portal_up = federation_portal_status(portal_name: @key.downcase.to_sym) render 'home/federation_portals_status' end diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 7412989fa5..0d7527fb50 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -117,39 +117,35 @@ def find_portal_name_by_api(api_url) portal ? portal[:name] : nil end - def federation_portals_status - federation_apis = federated_portals.map { |p| [ p.first , p.last[:api] ] } + def federation_portal_status(portal_name: nil) + portal_api = federated_portals[portal_name][:api] + tmp_portal_up = Rails.cache.read("federation_portal_up_#{portal_name}") - tmp_portals_up = Rails.cache.read('federation_portals_up') - if tmp_portals_up - return tmp_portals_up + if !tmp_portal_up.nil? + return tmp_portal_up else - portals_up = [] - federation_apis.each do |portal| - begin - conn = Faraday.new(url: portal.last) do |f| - f.request :url_encoded - f.adapter Faraday.default_adapter - f.options.timeout = 20 - f.options.open_timeout = 20 - end - response = conn.get - if response.success? - parsed_response = JSON.parse(response.body) - # todo: check if the response is a correct response - portals_up << portal.first - else - raise "API call failed with status #{response.status}" - end - - rescue => e - # timout or error + portal_up = false + begin + conn = Faraday.new(url: portal_api) do |f| + f.request :url_encoded + f.adapter Faraday.default_adapter + f.options.timeout = 20 + f.options.open_timeout = 20 + end + response = conn.get + if response.success? + parsed_response = JSON.parse(response.body) + # todo: check if the response is a correct response + portal_up = true + else + raise "API call failed with status #{response.status}" end - Rails.cache.write('federation_portals_up', portals_up, expires_in: 2.hours) - end - return portals_up + rescue => e + end + Rails.cache.write("federation_portal_up_#{portal_name}", portal_up, expires_in: 2.hours) + return portal_up end end diff --git a/app/views/home/federation_portals_status.html.haml b/app/views/home/federation_portals_status.html.haml index 972dc987fe..ecd73b11f8 100644 --- a/app/views/home/federation_portals_status.html.haml +++ b/app/views/home/federation_portals_status.html.haml @@ -1,6 +1,5 @@ -- portal_up = federation_portals_status.include?(@key.to_sym) = render TurboFrameComponent.new(id:"federation_portals_status_#{@key}") do |container| %div{style: "cursor: default;"} - = group_chip_component(name: "portals[]", object: { "acronym" => @acronym, "value" => @key }, checked: @checked, title: "#{!portal_up ? "#{@key.humanize.gsub('portal', 'Portal')} is currently down" : ''}", disabled: !portal_up) + = group_chip_component(name: "portals[]", object: { "acronym" => @acronym, "value" => @key }, checked: @checked, title: "#{!@portal_up ? "#{@key.humanize.gsub('portal', 'Portal')} is currently down" : ''}", disabled: !@portal_up) -# TODO put the portal is down tooltip message in local lang files \ No newline at end of file From d15ae626f2a2164f08b3f9d4e7a30eb60d461a8d Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 4 Oct 2024 11:39:30 +0200 Subject: [PATCH 50/72] use cached federation input chips in browse page --- app/assets/stylesheets/browse.scss | 12 ++++++++++++ app/controllers/home_controller.rb | 1 + app/views/home/federation_portals_status.html.haml | 2 +- app/views/ontologies/browser/browse.html.haml | 11 ++++++----- app/views/search/index.html.haml | 2 +- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/browse.scss b/app/assets/stylesheets/browse.scss index 2dc96db265..0cd58df72b 100644 --- a/app/assets/stylesheets/browse.scss +++ b/app/assets/stylesheets/browse.scss @@ -451,6 +451,18 @@ margin-top: 10px; } +.browse-federation-input-chips{ + display: flex; + flex-wrap: wrap; + margin: 0 15px 15px 15px; + div { + flex-grow: 1; + } + + label{ + display: grid; + } +} @media only screen and (max-width: 1250px) { .browse-first-row { diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index b1b2d6e9d3..da0166fcac 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -167,6 +167,7 @@ def annotator_recommender_form end def federation_portals_status + @name = params[:name] @acronym = params[:acronym] @key = params[:key] @checked = params[:checked].eql?('true') diff --git a/app/views/home/federation_portals_status.html.haml b/app/views/home/federation_portals_status.html.haml index ecd73b11f8..d8621ee3cf 100644 --- a/app/views/home/federation_portals_status.html.haml +++ b/app/views/home/federation_portals_status.html.haml @@ -1,5 +1,5 @@ = render TurboFrameComponent.new(id:"federation_portals_status_#{@key}") do |container| %div{style: "cursor: default;"} - = group_chip_component(name: "portals[]", object: { "acronym" => @acronym, "value" => @key }, checked: @checked, title: "#{!@portal_up ? "#{@key.humanize.gsub('portal', 'Portal')} is currently down" : ''}", disabled: !@portal_up) + = group_chip_component(name: @name, object: { "acronym" => @acronym, "value" => @key }, checked: @checked, title: "#{!@portal_up ? "#{@key.humanize.gsub('portal', 'Portal')} is currently down" : ''}", disabled: !@portal_up) -# TODO put the portal is down tooltip message in local lang files \ No newline at end of file diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index b0abb8cf94..ef5156d97c 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -71,11 +71,12 @@ = turbo_frame_tag "count_#{key}_#{link_last_part(object["id"])}", busy: true %span.show-if-loading = render LoaderComponent.new(small:true) - %div{data:{controller: "show-filter-count browse-filters", action: "change->show-filter-count#updateCount change->browse-filters#dispatchFilterEvent"}, id: "portals_filter_container"} - = render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: t('federation.results_from_external_portals')) do - .browse-filter-checks-container.px-1 - - federated_portals.each do |key, config| - = group_chip_component(name: "portals", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') + = render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: t('federation.results_from_external_portals')) do + .px-1.browse-federation-input-chips + - federated_portals.each do |key, config| + = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?name=portals&key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| + - container.loader do + = chips_skelton .browse-second-row .browse-search-bar diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index eb8f5e4736..a36f8d83ce 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -20,7 +20,7 @@ = t('federation.results_from_external_portals') .field.d-flex - federated_portals.each do |key, config| - = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| + = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?name=portals[]&key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| - container.loader do = chips_skelton .right From 6f989f6e9112fa69c5cbdbfc6e9753f9c4330c55 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 4 Oct 2024 11:49:31 +0200 Subject: [PATCH 51/72] fix browse page federation inputs section title style --- app/assets/stylesheets/browse.scss | 7 +++++++ app/views/ontologies/browser/browse.html.haml | 13 +++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/browse.scss b/app/assets/stylesheets/browse.scss index 0cd58df72b..a55292bde0 100644 --- a/app/assets/stylesheets/browse.scss +++ b/app/assets/stylesheets/browse.scss @@ -463,6 +463,13 @@ display: grid; } } +.browse-federation-input-chip-container{ + p{ + font-size: 14px; + font-weight: 400; + color: #666666; + } +} @media only screen and (max-width: 1250px) { .browse-first-row { diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index ef5156d97c..01e127d966 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -71,12 +71,13 @@ = turbo_frame_tag "count_#{key}_#{link_last_part(object["id"])}", busy: true %span.show-if-loading = render LoaderComponent.new(small:true) - = render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: t('federation.results_from_external_portals')) do - .px-1.browse-federation-input-chips - - federated_portals.each do |key, config| - = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?name=portals&key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| - - container.loader do - = chips_skelton + .browse-federation-input-chip-container + = render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: t('federation.results_from_external_portals')) do + .px-1.browse-federation-input-chips + - federated_portals.each do |key, config| + = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?name=portals&key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| + - container.loader do + = chips_skelton .browse-second-row .browse-search-bar From 3d2a63c294736cd56a717d0e9e4ccd17c96e4ac7 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Fri, 4 Oct 2024 13:00:28 +0200 Subject: [PATCH 52/72] initialize federation portals input chips in the home page (asynch in the background) --- app/views/home/index.html.haml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index fd32acc016..006e5a70fe 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -230,6 +230,13 @@ %a{href:logo[:url], target: "_blanc"} %img{src: asset_path(logo[:img_src])} +-# This section is hidden, used to initialize the cache of federation portals status +.d-none + - federated_portals.each do |key, config| + = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?name=portals[]&key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| + - container.loader do + = chips_skelton + :javascript function submitAnnotator(){ document.getElementById("annotator_submit").click() From 701879ad70675c3cef06f1c28815b645181b8c80 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Mon, 7 Oct 2024 16:07:33 +0200 Subject: [PATCH 53/72] clean federated search aggregator code --- Gemfile | 2 +- app/controllers/concerns/search_aggregator.rb | 5 +---- app/helpers/federation_helper.rb | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 683672fb59..768984099f 100644 --- a/Gemfile +++ b/Gemfile @@ -88,7 +88,7 @@ gem 'view_component', '~> 2.72' # Pagination library for Rails gem 'will_paginate', '~> 3.0' -# String similarity +# String similarity, used by federated search to rank results gem 'string-similarity' # Render SVG files in Rails views diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index a14e2bf088..3d7e52b2a1 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -85,16 +85,13 @@ def search_result_elem(class_object, ontology_acronym, title) ontology_acronym: ontology_acronym, link: link, definition: class_object.definition, + # Used for federation: portal_name: portal_name, portal_color: is_external ? federated_portal_color(portal_name) : nil, portal_light_color: is_external ? federated_portal_light_color(portal_name) : nil } end - def is_federation_external_class(class_object) - !class_object.links['self'].include?($REST_URL) - end - def ontology_name_acronym(ontologies, selected_acronym) ontology = ontologies.select { |x| x.acronym.eql?(selected_acronym.split('/').last) }.first "#{ontology.name} (#{ontology.acronym})" if ontology diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 0d7527fb50..698daaee64 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -103,6 +103,10 @@ def federatation_enabled? params[:federate] || params[:portals] end + def is_federation_external_class(class_object) + !class_object.links['self'].include?($REST_URL) + end + def portal_button(name: nil , color: nil , light_color: nil, link: nil, tooltip: nil) content_tag(:a, href: link, target: '_blank', 'data-controller': 'tooltip', title: tooltip, class: 'federation-portal-button button icon-right', style: color ? "background-color: #{light_color} !important" : '') do inline_svg_tag('logos/ontoportal.svg', class: "federated-icon-#{name.downcase}") + From 965cab561b82a07ff46e706a7eeb00d1b753354e Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Mon, 7 Oct 2024 16:22:07 +0200 Subject: [PATCH 54/72] move chip skelton to components helper --- app/helpers/application_helper.rb | 10 ---------- app/helpers/components_helper.rb | 11 ++++++++++- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ffc71d919b..ef2daf6b20 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -547,14 +547,4 @@ def categories_select(id: nil, name: nil, selected: 'None') categories_for_select = LinkedData::Client::Models::Category.all.map{|x| ["#{x.name} (#{x.acronym})", x.id]}.unshift(["None", '']) render Input::SelectComponent.new(id: id, name: name, value: categories_for_select, selected: selected) end - - def chips_skelton - content_tag(:div, class: 'chips-container loading') do - content_tag(:div) do - content_tag(:label) do - content_tag(:span, '', class: 'skeleton') - end - end - end - end end diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb index 0241c111fb..8aa36fc3d4 100644 --- a/app/helpers/components_helper.rb +++ b/app/helpers/components_helper.rb @@ -10,7 +10,7 @@ def alert_component(message, type: "info") render Display::AlertComponent.new(type: type, message: message) end - def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, disabled: false, &block) + def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, disabled: false, &block) content_tag(:div, data: { controller: 'tooltip' }, title: tooltip) do check_input(id: id, name: name, value: value, label: label, checked: checked, disabled: disabled, &block) end @@ -288,5 +288,14 @@ def form_cancel_button end end + def chips_skelton + content_tag(:div, class: 'chips-container loading') do + content_tag(:div) do + content_tag(:label) do + content_tag(:span, '', class: 'skeleton') + end + end + end + end end From f3cb39bbc89bd86cb73928efb53cf7a2bf716bb4 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Mon, 7 Oct 2024 16:22:33 +0200 Subject: [PATCH 55/72] internationalize portal is not responding message --- Gemfile | 2 ++ app/controllers/search_controller.rb | 2 +- app/helpers/federation_helper.rb | 2 +- config/locales/en.yml | 3 ++- config/locales/fr.yml | 2 ++ 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 768984099f..140d8debf6 100644 --- a/Gemfile +++ b/Gemfile @@ -93,6 +93,8 @@ gem 'string-similarity' # Render SVG files in Rails views gem 'inline_svg' + +# ISO language codes and flags gem "iso-639", "~> 0.3.6" gem "flag-icons-rails", "~> 3.4" gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'feature/federated-search' diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 222e2a3c0e..bceee9b3f8 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -23,7 +23,7 @@ def index @time = Benchmark.realtime do results = LinkedData::Client::Models::Class.search(@search_query, params) @federation_errors = results[:errors].map{|e| find_portal_name_by_api(e.split(' ').last.gsub('search', ''))} - @federation_errors = @federation_errors.map{ |p| "#{p} is not responding. " }.join(' ') + @federation_errors = @federation_errors.map{ |p| "#{p} #{t('federation.not_responding')} " }.join(' ') results = results[:results] @search_results = aggregate_results(@search_query, results) end diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 698daaee64..8dfad433f3 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -100,7 +100,7 @@ def request_portals_names end def federatation_enabled? - params[:federate] || params[:portals] + params[:portals] end def is_federation_external_class(class_object) diff --git a/config/locales/en.yml b/config/locales/en.yml index 10ce6d9f26..e99e3420c6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1505,4 +1505,5 @@ en: federation: results_from_external_portals: Results from external portals - from: from \ No newline at end of file + from: from + not_responding: is not responding. \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index edcfb71842..d5b33a5d7c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1543,3 +1543,5 @@ fr: federation: results_from_external_portals: Résultats provenant de portails externes from: de + not_responding: ne répond pas. + From fba0daf43b8550b5d8fca16721cc26ae8b131a8e Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Mon, 7 Oct 2024 16:29:18 +0200 Subject: [PATCH 56/72] clean federation portal status cache method --- app/helpers/federation_helper.rb | 34 ++++++++++---------------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 8dfad433f3..5f9a50b595 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -122,35 +122,21 @@ def find_portal_name_by_api(api_url) end def federation_portal_status(portal_name: nil) - portal_api = federated_portals[portal_name][:api] - tmp_portal_up = Rails.cache.read("federation_portal_up_#{portal_name}") - - if !tmp_portal_up.nil? - return tmp_portal_up - else + Rails.cache.fetch("federation_portal_up_#{portal_name}", expires_in: 2.hours) do + portal_api = federated_portals[portal_name][:api] portal_up = false begin - conn = Faraday.new(url: portal_api) do |f| - f.request :url_encoded - f.adapter Faraday.default_adapter + response = Faraday.new(url: portal_api) do |f| + f.request :url_encoded + f.adapter Faraday.default_adapter f.options.timeout = 20 f.options.open_timeout = 20 - end - response = conn.get - if response.success? - parsed_response = JSON.parse(response.body) - # todo: check if the response is a correct response - portal_up = true - else - raise "API call failed with status #{response.status}" - end - - rescue => e - + end.head + portal_up = response.success? + rescue StandardError => e + Rails.logger.error("Error checking portal status for #{portal_name}: #{e.message}") end - Rails.cache.write("federation_portal_up_#{portal_name}", portal_up, expires_in: 2.hours) - return portal_up + portal_up end - end end From b4f78166a5146b304dded1db491dfc5b017e0ecc Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Tue, 8 Oct 2024 23:42:31 +0200 Subject: [PATCH 57/72] clean federation stimulus controller code --- app/javascript/controllers/federation_controller.js | 1 - app/views/ontologies/browser/browse.html.haml | 4 ++-- app/views/search/index.html.haml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/javascript/controllers/federation_controller.js b/app/javascript/controllers/federation_controller.js index 7551f3d04b..432e5b5d72 100644 --- a/app/javascript/controllers/federation_controller.js +++ b/app/javascript/controllers/federation_controller.js @@ -5,7 +5,6 @@ export default class extends Controller { static values = { config: Object, } - static targets = ['chips'] connect() { this.#initIconsStyle() } diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index 01e127d966..05152ac363 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -1,5 +1,5 @@ .browse-center - .browse-container{'data-controller': 'federation', 'data-federation-config-value': federated_portals.to_h.to_json} + .browse-container .container.align-alert - if session[:user]&.admin? %div{style:'width: 70%;'} @@ -88,7 +88,7 @@ = options_for_select(@formats, @selected_format) %select#Sort_by.browse-sort-by-filter{:name => "Sort_by"} = options_for_select(@sorts_options, @sort_by) - .browse-ontologies + .browse-ontologies{'data-controller': 'federation', 'data-federation-config-value': federated_portals.to_h.to_json} = render TurboFrameComponent.new(id: "ontologies_list_view-page-1" , src: "/ontologies_filter?page=1&#{request.original_url.split('?').last}", data:{"turbo-frame-target":"frame", "turbo-frame-url-value": "/ontologies_filter"}) do |list| - list.loader do = browser_counter_loader diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index a36f8d83ce..22a5752be3 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -1,4 +1,4 @@ -.search-page-container{data: {turbo_frame: 'federation_portals_status'}} +.search-page-container .search-page-subcontainer{'data-controller': 'reveal-component'} = form_tag(search_path, method: :get,data:{turbo: true, controller: 'form-url', action: 'submit->form-url#submit'}) do = search_page_input_component(placeholder: t('search.search_place_holder'), name: "q", value: @search_query) do From 2a21b8cec6161cb99a6278a1730029efd94037d1 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Tue, 8 Oct 2024 23:44:41 +0200 Subject: [PATCH 58/72] display federated browse errors as warning instead of danger (orange instead of red) --- app/views/ontologies/browser/_ontologies.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/ontologies/browser/_ontologies.html.haml b/app/views/ontologies/browser/_ontologies.html.haml index 3ca8685bc0..cadbb85168 100644 --- a/app/views/ontologies/browser/_ontologies.html.haml +++ b/app/views/ontologies/browser/_ontologies.html.haml @@ -8,7 +8,7 @@ #{t("ontologies.showing_ontologies_size", ontologies_size: @count, analytics_size: @total_ontologies, portals: request_portals_names.join(', ').html_safe).html_safe} (#{sprintf("%.2f", @time)}s) - unless @errors.blank? %div.my-1 - = render Display::AlertComponent.new(type: 'danger') do + = render Display::AlertComponent.new(type: 'warning') do - @errors.each do |e| %div = e.errors || e From b037ab3e08f3cf47c4d6db45582a2f51e6d9275b Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 9 Oct 2024 00:13:57 +0200 Subject: [PATCH 59/72] remove related code to fedrated portals status to put it in another PR --- app/assets/stylesheets/browse.scss | 19 ---------- app/assets/stylesheets/components/chips.scss | 38 +------------------ app/components/chips_component.rb | 3 +- app/controllers/home_controller.rb | 9 ----- app/helpers/components_helper.rb | 18 ++------- app/helpers/federation_helper.rb | 18 --------- app/helpers/inputs_helper.rb | 4 +- .../home/federation_portals_status.html.haml | 5 --- app/views/home/index.html.haml | 7 ---- app/views/ontologies/browser/browse.html.haml | 6 +-- app/views/search/index.html.haml | 4 +- config/routes.rb | 1 - 12 files changed, 11 insertions(+), 121 deletions(-) delete mode 100644 app/views/home/federation_portals_status.html.haml diff --git a/app/assets/stylesheets/browse.scss b/app/assets/stylesheets/browse.scss index a55292bde0..2dc96db265 100644 --- a/app/assets/stylesheets/browse.scss +++ b/app/assets/stylesheets/browse.scss @@ -451,25 +451,6 @@ margin-top: 10px; } -.browse-federation-input-chips{ - display: flex; - flex-wrap: wrap; - margin: 0 15px 15px 15px; - div { - flex-grow: 1; - } - - label{ - display: grid; - } -} -.browse-federation-input-chip-container{ - p{ - font-size: 14px; - font-weight: 400; - color: #666666; - } -} @media only screen and (max-width: 1250px) { .browse-first-row { diff --git a/app/assets/stylesheets/components/chips.scss b/app/assets/stylesheets/components/chips.scss index 770a41a3f9..267fbb00db 100644 --- a/app/assets/stylesheets/components/chips.scss +++ b/app/assets/stylesheets/components/chips.scss @@ -32,7 +32,7 @@ } .chips-container.disabled div label > span, .chips-container div label span:has(span.disabled){ - opacity: 60%; + background-color: #f8f9fa !important; } .chips-container div label input[type="checkbox"]:checked ~ span{ @@ -43,39 +43,3 @@ .chips-container div label input[type="checkbox"]:checked ~ span .chips-check-icon{ display:unset; } - -.chips-container.loading div { - cursor: default; - opacity: 0.6; -} - -.chips-container .skeleton { - width: 80px; - height: 36px; - background-color: #e0e0e0; - border-radius: 5px; - animation: shimmer 4s infinite; - position: relative; - overflow: hidden; -} - -@keyframes shimmer { - 0% { - background-position: -200px 0; - } - 100% { - background-position: 200px 0; - } -} - -.chips-container .skeleton::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0.2) 100%); - animation: shimmer 4s infinite; - border-radius: 5px; -} diff --git a/app/components/chips_component.rb b/app/components/chips_component.rb index b97ddb3bbc..b92080d79c 100644 --- a/app/components/chips_component.rb +++ b/app/components/chips_component.rb @@ -1,14 +1,13 @@ class ChipsComponent < ViewComponent::Base renders_one :count - def initialize(id:nil, name:, label: nil, value: nil, checked: false, tooltip: nil, disabled: false) + def initialize(id:nil, name:, label: nil, value: nil, checked: false, tooltip: nil) @id = id || name @name = name @value = value || 'true' @checked = checked @label = label || @value @tooltip = tooltip - @disabled = disabled end def checked? diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index da0166fcac..bc9531e562 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -166,15 +166,6 @@ def annotator_recommender_form end end - def federation_portals_status - @name = params[:name] - @acronym = params[:acronym] - @key = params[:key] - @checked = params[:checked].eql?('true') - @portal_up = federation_portal_status(portal_name: @key.downcase.to_sym) - render 'home/federation_portals_status' - end - private # Dr. Musen wants 5 specific groups to appear first, sorted by order of importance. diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb index 8aa36fc3d4..efef766868 100644 --- a/app/helpers/components_helper.rb +++ b/app/helpers/components_helper.rb @@ -10,19 +10,19 @@ def alert_component(message, type: "info") render Display::AlertComponent.new(type: type, message: message) end - def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, disabled: false, &block) + def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, &block) content_tag(:div, data: { controller: 'tooltip' }, title: tooltip) do - check_input(id: id, name: name, value: value, label: label, checked: checked, disabled: disabled, &block) + check_input(id: id, name: name, value: value, label: label, checked: checked, &block) end end - def group_chip_component(id: nil, name: , object: , checked: , value: nil, title: nil, disabled: false, &block) + def group_chip_component(id: nil, name: , object: , checked: , value: nil, title: nil, &block) title ||= object["name"] value ||= (object["value"] || object["acronym"] || object["id"]) chips_component(id: id || value, name: name, label: object["acronym"], checked: checked, - value: value, tooltip: title, disabled: disabled, &block) + value: value, tooltip: title, &block) end alias :category_chip_component :group_chip_component @@ -288,14 +288,4 @@ def form_cancel_button end end - def chips_skelton - content_tag(:div, class: 'chips-container loading') do - content_tag(:div) do - content_tag(:label) do - content_tag(:span, '', class: 'skeleton') - end - end - end - end - end diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 5f9a50b595..52b40e2465 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -121,22 +121,4 @@ def find_portal_name_by_api(api_url) portal ? portal[:name] : nil end - def federation_portal_status(portal_name: nil) - Rails.cache.fetch("federation_portal_up_#{portal_name}", expires_in: 2.hours) do - portal_api = federated_portals[portal_name][:api] - portal_up = false - begin - response = Faraday.new(url: portal_api) do |f| - f.request :url_encoded - f.adapter Faraday.default_adapter - f.options.timeout = 20 - f.options.open_timeout = 20 - end.head - portal_up = response.success? - rescue StandardError => e - Rails.logger.error("Error checking portal status for #{portal_name}: #{e.message}") - end - portal_up - end - end end diff --git a/app/helpers/inputs_helper.rb b/app/helpers/inputs_helper.rb index 242de76990..94b677ccd2 100644 --- a/app/helpers/inputs_helper.rb +++ b/app/helpers/inputs_helper.rb @@ -28,8 +28,8 @@ def number_input(name: , label: '', value: ) value: value) end - def check_input(id:, name:, value:, label: '', checked: false, disabled: false, &block) - render ChipsComponent.new(name: name, id: id, label: label, value: value, checked: checked, disabled: disabled) do |c| + def check_input(id:, name:, value:, label: '', checked: false, &block) + render ChipsComponent.new(name: name, id: id, label: label, value: value, checked: checked) do |c| if block_given? capture(c, &block) end diff --git a/app/views/home/federation_portals_status.html.haml b/app/views/home/federation_portals_status.html.haml deleted file mode 100644 index d8621ee3cf..0000000000 --- a/app/views/home/federation_portals_status.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -= render TurboFrameComponent.new(id:"federation_portals_status_#{@key}") do |container| - %div{style: "cursor: default;"} - = group_chip_component(name: @name, object: { "acronym" => @acronym, "value" => @key }, checked: @checked, title: "#{!@portal_up ? "#{@key.humanize.gsub('portal', 'Portal')} is currently down" : ''}", disabled: !@portal_up) - --# TODO put the portal is down tooltip message in local lang files \ No newline at end of file diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 006e5a70fe..fd32acc016 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -230,13 +230,6 @@ %a{href:logo[:url], target: "_blanc"} %img{src: asset_path(logo[:img_src])} --# This section is hidden, used to initialize the cache of federation portals status -.d-none - - federated_portals.each do |key, config| - = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?name=portals[]&key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| - - container.loader do - = chips_skelton - :javascript function submitAnnotator(){ document.getElementById("annotator_submit").click() diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index 05152ac363..0bb240dcc3 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -73,11 +73,9 @@ = render LoaderComponent.new(small:true) .browse-federation-input-chip-container = render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: t('federation.results_from_external_portals')) do - .px-1.browse-federation-input-chips + .browse-filter-checks-container.px-1 - federated_portals.each do |key, config| - = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?name=portals&key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| - - container.loader do - = chips_skelton + = group_chip_component(name: "portals", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') .browse-second-row .browse-search-bar diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml index 22a5752be3..d5f117cf13 100644 --- a/app/views/search/index.html.haml +++ b/app/views/search/index.html.haml @@ -20,9 +20,7 @@ = t('federation.results_from_external_portals') .field.d-flex - federated_portals.each do |key, config| - = render TurboFrameComponent.new(id: "federation_portals_status_#{key}", src: "home/federation_portals_status?name=portals[]&key=#{key}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}") do |container| - - container.loader do - = chips_skelton + = group_chip_component(name: "portals[]", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '') .right .filter-container .title diff --git a/config/routes.rb b/config/routes.rb index 226b6d0ca5..b28c26ec02 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -131,7 +131,6 @@ end get '' => 'home#index' - get 'home/federation_portals_status', to: 'home#federation_portals_status' match 'sparql_proxy', to: 'admin#sparql_endpoint', via: [:get, :post] From 69fe323011da48be904b469d586f465d81938b9b Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 9 Oct 2024 01:46:54 +0200 Subject: [PATCH 60/72] use the key "collection" instead of "results" in federated search result hash to maintain the endpoint expected schema --- app/controllers/search_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index bceee9b3f8..5cfd4c59a1 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -24,7 +24,7 @@ def index results = LinkedData::Client::Models::Class.search(@search_query, params) @federation_errors = results[:errors].map{|e| find_portal_name_by_api(e.split(' ').last.gsub('search', ''))} @federation_errors = @federation_errors.map{ |p| "#{p} #{t('federation.not_responding')} " }.join(' ') - results = results[:results] + results = results[:collection] @search_results = aggregate_results(@search_query, results) end @advanced_options_open = !search_params_empty? From 71f92bfa2bbc12a980c94f949e67d00fc2ccdc9b Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 9 Oct 2024 19:16:36 +0200 Subject: [PATCH 61/72] method to get the canonical portal for an ontology using pullLocation --- app/helpers/federation_helper.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 52b40e2465..528ec50b62 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -121,4 +121,25 @@ def find_portal_name_by_api(api_url) portal ? portal[:name] : nil end + def canonical_portal(ontologies) + portals = federated_portals.map{|portal| portal.first} + portal_counts = Hash.new(0) + # Count occurrences of each portal in the pull_location URL + ontologies.each do |ontology| + portals.each do |portal| + if ontology[:pullLocation]&.include?(portal.to_s) + portal_counts[portal] += 1 + end + end + end + # Determine the portal with the most occurrences + canonical_portal = portal_counts.max_by { |_, count| count }&.first + + canonical_portal + end + + def ontology_from_portal(ontologies, portal) + ontologies.select{|o| o[:id].include?(portal.to_s)}.first + end + end From 108ed98c0c1ff89d352c999d63fd0aa030e989bd Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 9 Oct 2024 19:17:11 +0200 Subject: [PATCH 62/72] display federated ontologies in browse page based on the canonical portal --- app/controllers/concerns/submission_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index b37beeb8d4..579996ceb0 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -71,7 +71,7 @@ def merge_by_acronym(submissions) if ontologies.size.eql?(1) ontology = ontologies.first else - ontology = ontologies.select { |x| helpers.internal_ontology?(x[:id]) }.first || ontologies.first + ontology = ontologies.select { |x| helpers.internal_ontology?(x[:id]) }.first || ontology_from_portal(ontologies, canonical_portal(ontologies)) end ontology[:sources] = ontologies.map { |x| x[:id] } From 2f4c8c4d2ee07013b52f6a0df0fb5eabd86157f4 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 16 Oct 2024 20:57:59 +0200 Subject: [PATCH 63/72] clean canonical portal function in federation helper --- app/helpers/federation_helper.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 19847f5999..d06e96f7b0 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -125,18 +125,17 @@ def federation_external_class?(class_object) def canonical_portal(ontologies) - portals = federated_portals.map{|portal| portal.first} portal_counts = Hash.new(0) # Count occurrences of each portal in the pull_location URL - portals.each do |portal| - if ontology[:pullLocation]&.include?(portal.to_s) ontologies.each do |ontology| + federated_portals.keys.each do |portal| + if ontology[:pullLocation]&.include?(portal.to_s) portal_counts[portal] += 1 - end end + end end - canonical_portal = portal_counts.max_by { |_, count| count }&.first # Determine the portal with the most occurrences + canonical_portal = portal_counts.max_by { |_, count| count }&.first canonical_portal end From 61d8cfd30dcc4576adb2d3775b3850aaaecfc037 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 16 Oct 2024 21:05:25 +0200 Subject: [PATCH 64/72] extract canonical portal ontology choice in a function for clarity --- app/controllers/concerns/submission_filter.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index 579996ceb0..fd7becd364 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -71,7 +71,7 @@ def merge_by_acronym(submissions) if ontologies.size.eql?(1) ontology = ontologies.first else - ontology = ontologies.select { |x| helpers.internal_ontology?(x[:id]) }.first || ontology_from_portal(ontologies, canonical_portal(ontologies)) + ontology = canonical_portal_ontology(ontologies) end ontology[:sources] = ontologies.map { |x| x[:id] } @@ -80,6 +80,10 @@ def merge_by_acronym(submissions) merged_submissions end + def canonical_portal_ontology(ontologies) + ontologies.select { |x| helpers.internal_ontology?(x[:id]) }.first || ontology_from_portal(ontologies, canonical_portal(ontologies)) + end + def filter_submissions(ontologies, query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) submissions = LinkedData::Client::Models::OntologySubmission.all(include: BROWSE_ATTRIBUTES.join(','), also_include_views: true, display_links: false, display_context: false) From 4dbba55e415d9752c245635807cff384d240773a Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Thu, 17 Oct 2024 16:41:11 +0200 Subject: [PATCH 65/72] display search page results based on the canonical portal of the ontology --- app/controllers/concerns/search_aggregator.rb | 4 +++ app/helpers/federation_helper.rb | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index e8a72a74ed..2a77e4dbef 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -31,12 +31,16 @@ def aggregate_results(query, results) all_ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym,name', include_views: true, display_links: false, display_context: false) + all_submissions = LinkedData::Client::Models::OntologySubmission.all(include: 'pullLocation', include_views: true, display_links: false, display_context: false) + search_results = grouped_results.map do |group| format_search_result(group, all_ontologies) end search_results = merge_sort_federated_results(query, search_results) if federation_enabled? + search_results = apply_canonical_portal(search_results, all_submissions) + search_results end diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index d06e96f7b0..01c84e0148 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -140,6 +140,37 @@ def canonical_portal(ontologies) canonical_portal end + def apply_canonical_portal(search_results, all_submissions) + search_results.each do |result| + next if result[:root][:portal_name].nil? || result[:root][:other_portals].blank? + + root_link = result[:root][:link].split('?').first + candidates = [result[:root][:link]] + result[:root][:other_portals].map { |p| p[:link].split('?').first } + + portal_counts = Hash.new(0) + candidates.each do |candidate| + submission = all_submissions.find { |s| s.id.split('/')[0...-2].join('/') == candidate } + + federated_portals.keys.each do |portal| + portal_counts[portal] += 1 if submission&.include?(portal.to_s) + end + end + + canonical_portal = portal_counts.max_by { |_, count| count }&.first + next if canonical_portal.nil? || result[:root][:portal_name].eql?(canonical_portal.to_s) + + canonical_portal_result = result[:root][:other_portals].find { |r| r[:name] == canonical_portal.to_s } + swap_portal_attributes(result[:root], canonical_portal_result) if canonical_portal_result + end + end + + def swap_portal_attributes(root_portal, new_portal) + root_portal[:link], new_portal[:link] = new_portal[:link], root_portal[:link] + root_portal[:portal_name], new_portal[:portal_name] = new_portal[:portal_name], root_portal[:portal_name] + root_portal[:portal_color], new_portal[:portal_color] = new_portal[:portal_color], root_portal[:portal_color] + root_portal[:portal_light_color], new_portal[:portal_light_color] = new_portal[:portal_light_color], root_portal[:portal_light_color] + end + def ontology_from_portal(ontologies, portal) ontologies.select{|o| o[:id].include?(portal.to_s)}.first end From 1d748c8e72f71c6300552923a8573cd31cd73ae6 Mon Sep 17 00:00:00 2001 From: Syphax Date: Tue, 22 Oct 2024 08:05:48 +0200 Subject: [PATCH 66/72] move federation canonical to federation helper --- app/controllers/concerns/submission_filter.rb | 10 +------- app/helpers/federation_helper.rb | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index fd7becd364..c35fadd950 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -68,21 +68,13 @@ def ontologies_with_filters_url(filters, page: 1, count: false) def merge_by_acronym(submissions) merged_submissions = [] submissions.group_by { |x| x[:ontology]&.acronym }.each do |acronym, ontologies| - if ontologies.size.eql?(1) - ontology = ontologies.first - else - ontology = canonical_portal_ontology(ontologies) - end - + ontology = canonical_ontology(ontologies) ontology[:sources] = ontologies.map { |x| x[:id] } merged_submissions << ontology end merged_submissions end - def canonical_portal_ontology(ontologies) - ontologies.select { |x| helpers.internal_ontology?(x[:id]) }.first || ontology_from_portal(ontologies, canonical_portal(ontologies)) - end def filter_submissions(ontologies, query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) submissions = LinkedData::Client::Models::OntologySubmission.all(include: BROWSE_ATTRIBUTES.join(','), also_include_views: true, display_links: false, display_context: false) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 01c84e0148..67d4a40eae 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -123,8 +123,20 @@ def federation_external_class?(class_object) !class_object.links['self'].include?($REST_URL) end + def canonical_ontology(ontologies) + if ontologies.size.eql?(1) + ontologies.first + else + internal_ontology = ontologies.select { |x| helpers.internal_ontology?(x[:id]) }.first + if internal_ontology + internal_ontology + else + external_canonical_ontology_portal(ontologies) + end + end + end - def canonical_portal(ontologies) + def external_canonical_ontology_portal(ontologies) portal_counts = Hash.new(0) # Count occurrences of each portal in the pull_location URL ontologies.each do |ontology| @@ -135,16 +147,15 @@ def canonical_portal(ontologies) end end # Determine the portal with the most occurrences - canonical_portal = portal_counts.max_by { |_, count| count }&.first + portal = portal_counts.max_by { |_, count| count }&.first - canonical_portal + ontologies.select{|o| o[:id].include?(portal.to_s)}.first end def apply_canonical_portal(search_results, all_submissions) search_results.each do |result| next if result[:root][:portal_name].nil? || result[:root][:other_portals].blank? - root_link = result[:root][:link].split('?').first candidates = [result[:root][:link]] + result[:root][:other_portals].map { |p| p[:link].split('?').first } portal_counts = Hash.new(0) @@ -171,8 +182,4 @@ def swap_portal_attributes(root_portal, new_portal) root_portal[:portal_light_color], new_portal[:portal_light_color] = new_portal[:portal_light_color], root_portal[:portal_light_color] end - def ontology_from_portal(ontologies, portal) - ontologies.select{|o| o[:id].include?(portal.to_s)}.first - end - end From 6a2aba4e000da760930d2dd2ed74047575f77121 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 23 Oct 2024 10:00:48 +0200 Subject: [PATCH 67/72] clean swap portal attributes function in federation helper --- app/helpers/federation_helper.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 67d4a40eae..db99831d18 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -176,10 +176,9 @@ def apply_canonical_portal(search_results, all_submissions) end def swap_portal_attributes(root_portal, new_portal) - root_portal[:link], new_portal[:link] = new_portal[:link], root_portal[:link] - root_portal[:portal_name], new_portal[:portal_name] = new_portal[:portal_name], root_portal[:portal_name] - root_portal[:portal_color], new_portal[:portal_color] = new_portal[:portal_color], root_portal[:portal_color] - root_portal[:portal_light_color], new_portal[:portal_light_color] = new_portal[:portal_light_color], root_portal[:portal_light_color] + [:link, :portal_name, :portal_color, :portal_light_color].each do |attribute| + root_portal[attribute], new_portal[attribute] = new_portal[attribute], root_portal[attribute] + end end end From a5c855f1ccda48b17c8d3fcae75cfaffeafb8ec1 Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 23 Oct 2024 11:06:39 +0200 Subject: [PATCH 68/72] fix and clean apply canincal portal function --- app/helpers/federation_helper.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index db99831d18..38453c7360 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -141,9 +141,7 @@ def external_canonical_ontology_portal(ontologies) # Count occurrences of each portal in the pull_location URL ontologies.each do |ontology| federated_portals.keys.each do |portal| - if ontology[:pullLocation]&.include?(portal.to_s) - portal_counts[portal] += 1 - end + portal_counts[portal] += 1 if ontology[:pullLocation]&.include?(portal.to_s) end end # Determine the portal with the most occurrences @@ -156,14 +154,18 @@ def apply_canonical_portal(search_results, all_submissions) search_results.each do |result| next if result[:root][:portal_name].nil? || result[:root][:other_portals].blank? - candidates = [result[:root][:link]] + result[:root][:other_portals].map { |p| p[:link].split('?').first } + candidates = [result[:root][:link].split('?').first] + result[:root][:other_portals].map { |p| p[:link].split('?').first } portal_counts = Hash.new(0) + candidates.each do |candidate| - submission = all_submissions.find { |s| s.id.split('/')[0...-2].join('/') == candidate } - federated_portals.keys.each do |portal| - portal_counts[portal] += 1 if submission&.include?(portal.to_s) + submission = all_submissions.find { |s| s.id&.include?(candidate.split('/').last) } + + if submission + federated_portals.keys.each do |portal| + portal_counts[portal] += 1 if submission[:pullLocation]&.include?(portal.to_s) + end end end @@ -173,6 +175,7 @@ def apply_canonical_portal(search_results, all_submissions) canonical_portal_result = result[:root][:other_portals].find { |r| r[:name] == canonical_portal.to_s } swap_portal_attributes(result[:root], canonical_portal_result) if canonical_portal_result end + return search_results end def swap_portal_attributes(root_portal, new_portal) From 8b2182f10e8d50262b620bcf989a6e7ccc3499ac Mon Sep 17 00:00:00 2001 From: Bilel KIHAL Date: Wed, 23 Oct 2024 11:18:55 +0200 Subject: [PATCH 69/72] extract count portals into a separate function from external_canonical_ontology_portal function + apply_canonical_portal function --- app/helpers/federation_helper.rb | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 38453c7360..4ba324c0e1 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -136,17 +136,19 @@ def canonical_ontology(ontologies) end end - def external_canonical_ontology_portal(ontologies) + def count_portals(ontologies_list) portal_counts = Hash.new(0) - # Count occurrences of each portal in the pull_location URL - ontologies.each do |ontology| + ontologies_list.each do |ontology| federated_portals.keys.each do |portal| portal_counts[portal] += 1 if ontology[:pullLocation]&.include?(portal.to_s) end end - # Determine the portal with the most occurrences - portal = portal_counts.max_by { |_, count| count }&.first + portal_counts + end + def external_canonical_ontology_portal(ontologies) + portal_counts = count_portals(ontologies) + portal = portal_counts.max_by { |_, count| count }&.first ontologies.select{|o| o[:id].include?(portal.to_s)}.first end @@ -156,19 +158,14 @@ def apply_canonical_portal(search_results, all_submissions) candidates = [result[:root][:link].split('?').first] + result[:root][:other_portals].map { |p| p[:link].split('?').first } - portal_counts = Hash.new(0) - + ontologies_list = [] candidates.each do |candidate| - submission = all_submissions.find { |s| s.id&.include?(candidate.split('/').last) } - - if submission - federated_portals.keys.each do |portal| - portal_counts[portal] += 1 if submission[:pullLocation]&.include?(portal.to_s) - end - end + ontologies_list << submission if submission end + portal_counts = count_portals(ontologies_list) + canonical_portal = portal_counts.max_by { |_, count| count }&.first next if canonical_portal.nil? || result[:root][:portal_name].eql?(canonical_portal.to_s) From 55d0b716707e17451b797a7786201a99c5559a93 Mon Sep 17 00:00:00 2001 From: Syphax Date: Wed, 23 Oct 2024 21:27:11 +0200 Subject: [PATCH 70/72] put again search canonical logic the search_aggregator file and refactor the code --- app/controllers/concerns/search_aggregator.rb | 40 ++++++++++-- app/helpers/federation_helper.rb | 62 +++++-------------- 2 files changed, 53 insertions(+), 49 deletions(-) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index b2f19a3438..870bf50667 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -39,9 +39,7 @@ def aggregate_results(query, results) search_results = merge_sort_federated_results(query, search_results) if federation_enabled? - search_results = apply_canonical_portal(search_results, all_submissions) - - search_results + swap_canonical_portal_results_first(search_results, all_submissions) end def format_search_result(result, ontologies) @@ -255,7 +253,13 @@ def merge_federated_results(search_results) if (element[:root][:ontology_acronym] == reuse[:root][:ontology_acronym]) && (element[:root][:uri] == reuse[:root][:uri]) portal_name = reuse[:root][:portal_name] link = reuse[:root][:link] - element[:root][:other_portals] << {name: portal_name, color: federated_portal_color(portal_name), light_color: federated_portal_light_color(portal_name), link: link} + element[:root][:other_portals] << { + name: portal_name, + color: federated_portal_color(portal_name), + light_color: federated_portal_light_color(portal_name), + link: link, + ontology_id: reuse[:root][:ontology_id] + } true else false @@ -264,6 +268,34 @@ def merge_federated_results(search_results) end end + def swap_canonical_portal_results_first(search_results, all_submissions) + search_results.each do |result| + next if result[:root][:portal_name].nil? || result[:root][:other_portals].blank? + + result_ontology_ids = [result[:root][:ontology_id]] + result[:root][:other_portals].map { |p| p[:ontology_id] } + + result_submissions = all_submissions.select do |submission| + result_ontology_ids.any? { |ontology_id| submission.id.include?(ontology_id) } + end + + canonical_portal = most_referred_portal(result_submissions) + is_internal_ontology = result[:root][:portal_name].eql?(canonical_portal.to_s) + + next if canonical_portal.nil? || is_internal_ontology + + canonical_portal_result = result[:root][:other_portals].find { |r| r[:name] == canonical_portal.to_s } + swap_portal_attributes(result[:root], canonical_portal_result) if canonical_portal_result + end + search_results + end + + + def swap_portal_attributes(root_portal, new_portal) + [:link, :portal_name, :portal_color, :portal_light_color].each do |attribute| + root_portal[attribute], new_portal[attribute] = new_portal[attribute], root_portal[attribute] + end + end + def sort_results_by_string_similarity(query, search_results) search_results = search_results.sort_by do |entry| root_similarity = String::Similarity.cosine(query.downcase, entry[:root][:title].split('-').first.gsub(" ", "").downcase) diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb index 136be8b615..135cae2883 100644 --- a/app/helpers/federation_helper.rb +++ b/app/helpers/federation_helper.rb @@ -148,51 +148,6 @@ def canonical_ontology(ontologies) end end - def count_portals(ontologies_list) - portal_counts = Hash.new(0) - ontologies_list.each do |ontology| - federated_portals.keys.each do |portal| - portal_counts[portal] += 1 if ontology[:pullLocation]&.include?(portal.to_s) - end - end - portal_counts - end - - def external_canonical_ontology_portal(ontologies) - portal_counts = count_portals(ontologies) - portal = portal_counts.max_by { |_, count| count }&.first - ontologies.select{|o| o[:id].include?(portal.to_s)}.first - end - - def apply_canonical_portal(search_results, all_submissions) - search_results.each do |result| - next if result[:root][:portal_name].nil? || result[:root][:other_portals].blank? - - candidates = [result[:root][:link].split('?').first] + result[:root][:other_portals].map { |p| p[:link].split('?').first } - - ontologies_list = [] - candidates.each do |candidate| - submission = all_submissions.find { |s| s.id&.include?(candidate.split('/').last) } - ontologies_list << submission if submission - end - - portal_counts = count_portals(ontologies_list) - - canonical_portal = portal_counts.max_by { |_, count| count }&.first - next if canonical_portal.nil? || result[:root][:portal_name].eql?(canonical_portal.to_s) - - canonical_portal_result = result[:root][:other_portals].find { |r| r[:name] == canonical_portal.to_s } - swap_portal_attributes(result[:root], canonical_portal_result) if canonical_portal_result - end - return search_results - end - - def swap_portal_attributes(root_portal, new_portal) - [:link, :portal_name, :portal_color, :portal_light_color].each do |attribute| - root_portal[attribute], new_portal[attribute] = new_portal[attribute], root_portal[attribute] - end - end - def federation_portal_status(portal_name: nil) Rails.cache.fetch("federation_portal_up_#{portal_name}", expires_in: 2.hours) do portal_api = federated_portals&.dig(portal_name,:api) @@ -248,6 +203,7 @@ def init_federation_portals_status federation_input_chips end end + def federated_search_counts(search_results) ids = search_results.map do |result| result.dig(:root, :ontology_id) || rest_url @@ -276,4 +232,20 @@ def counts_ontology_ids_by_portal_name(portals_ids) counts end + + def external_canonical_ontology_portal(ontologies) + canonical_portal = most_referred_portal(ontologies) + ontologies.select{|o| o[:id].include?(canonical_portal.to_s)}.first + end + + def most_referred_portal(ontology_submissions) + portal_counts = Hash.new(0) + ontology_submissions.each do |submission| + federated_portals.keys.each do |portal| + portal_counts[portal] += 1 if submission[:pullLocation]&.include?(portal.to_s) + end + end + portal_counts.max_by { |_, count| count }&.first + end + end From a86ba778d8783d03756f373dc339501fb704b38c Mon Sep 17 00:00:00 2001 From: Syphax Date: Wed, 23 Oct 2024 21:30:47 +0200 Subject: [PATCH 71/72] do the search canonical logic only if enabled --- app/controllers/concerns/search_aggregator.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb index 870bf50667..24cc2b292c 100644 --- a/app/controllers/concerns/search_aggregator.rb +++ b/app/controllers/concerns/search_aggregator.rb @@ -31,15 +31,17 @@ def aggregate_results(query, results) all_ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym,name', include_views: true, display_links: false, display_context: false) - all_submissions = LinkedData::Client::Models::OntologySubmission.all(include: 'pullLocation', include_views: true, display_links: false, display_context: false) search_results = grouped_results.map do |group| format_search_result(group, all_ontologies) end - search_results = merge_sort_federated_results(query, search_results) if federation_enabled? + if federation_enabled? + search_results = merge_sort_federated_results(query, search_results) if federation_enabled? + search_results = swap_canonical_portal_results_first(search_results) + end - swap_canonical_portal_results_first(search_results, all_submissions) + search_results end def format_search_result(result, ontologies) @@ -268,7 +270,9 @@ def merge_federated_results(search_results) end end - def swap_canonical_portal_results_first(search_results, all_submissions) + def swap_canonical_portal_results_first(search_results) + all_submissions = LinkedData::Client::Models::OntologySubmission.all(include: 'pullLocation', include_views: true, display_links: false, display_context: false) + search_results.each do |result| next if result[:root][:portal_name].nil? || result[:root][:other_portals].blank? From 5a533f1c37310f9de5cd5565533fba252db062e6 Mon Sep 17 00:00:00 2001 From: Syphax Date: Wed, 23 Oct 2024 21:45:12 +0200 Subject: [PATCH 72/72] fix rest_hostname raising an exception that REST_URI does not exist --- app/helpers/urls_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/urls_helper.rb b/app/helpers/urls_helper.rb index b193f0a084..2c149e29d4 100644 --- a/app/helpers/urls_helper.rb +++ b/app/helpers/urls_helper.rb @@ -5,7 +5,7 @@ def url_to_endpoint(url) endpoint end def rest_hostname - extract_hostname(REST_URI) + extract_hostname($REST_URL) end def extract_hostname(url)