diff --git a/Gemfile b/Gemfile index c40e3640c..781e6379e 100644 --- a/Gemfile +++ b/Gemfile @@ -96,7 +96,7 @@ gem 'flag-icons-rails', '~> 3.4' gem 'iso-639', '~> 0.3.6' # Custom API client -gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'master' +gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'development' # 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 51f97081a..2c0afc74b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,10 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 24fb2549f7b69841e052491439bc8375ed5acfd9 - branch: master + revision: 15ad7d41d9a414ed3570c073c2b75a3fb4cc19cf + branch: development 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 @@ -420,6 +422,8 @@ GEM regexp_parser (2.9.2) reline (0.5.10) 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) @@ -485,7 +489,7 @@ GEM simplecov-cobertura (2.1.0) rexml simplecov (~> 0.19) - simplecov-html (0.13.0) + simplecov-html (0.13.1) simplecov_json_formatter (0.1.4) snaky_hash (2.0.1) hashie diff --git a/app/assets/stylesheets/components/chip_button.scss b/app/assets/stylesheets/components/chip_button.scss index 1b19a2981..18916b7fe 100644 --- a/app/assets/stylesheets/components/chip_button.scss +++ b/app/assets/stylesheets/components/chip_button.scss @@ -10,6 +10,7 @@ } .chip-button-component-container { display: inline-block; + text-wrap: nowrap !important; } .chip-button-component-container svg path{ fill: var(--primary-color); diff --git a/app/components/display/header_component.rb b/app/components/display/header_component.rb index 76784068e..d8b8ecebd 100644 --- a/app/components/display/header_component.rb +++ b/app/components/display/header_component.rb @@ -4,7 +4,6 @@ class Display::HeaderComponent < ViewComponent::Base include ComponentsHelper - renders_one :text def initialize(text: nil, tooltip: nil) super @@ -14,7 +13,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 + info_tooltip(content_tag(:div, @info, style: 'max-width: 300px')) end diff --git a/app/components/dropdown_container_component.rb b/app/components/dropdown_container_component.rb index e06b3fa7a..f9d8ce38d 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 7b9d4965e..398f3199a 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} diff --git a/app/components/ontology_browse_card_component.rb b/app/components/ontology_browse_card_component.rb index 84251d7f0..dfc2c05d0 100644 --- a/app/components/ontology_browse_card_component.rb +++ b/app/components/ontology_browse_card_component.rb @@ -1,14 +1,40 @@ # frozen_string_literal: true class OntologyBrowseCardComponent < ViewComponent::Base - include OntologiesHelper + include ApplicationHelper, OntologiesHelper, FederationHelper - 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? + !internal_ontology?(@ontology[:id]) || (Array(@ontology[:sources]).size > 1) + 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 aaf97e85f..6a08ccc83 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 #{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)) @@ -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 + .d-flex.w-100.mt-1.flex-wrap - 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,32 @@ - 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? + - 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] - 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/components/square_badge_component.rb b/app/components/square_badge_component.rb index 9d85a8c34..bfbc95c6d 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 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f75a395b7..291e2ae33 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -175,6 +175,9 @@ def bp_config_json def rest_url helpers.rest_url end + def request_portals + helpers.request_portals + end def parse_response_body(response) return nil if response.nil? diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb index 45746e36e..b37beeb8d 100644 --- a/app/controllers/concerns/submission_filter.rb +++ b/app/controllers/concerns/submission_filter.rb @@ -1,7 +1,7 @@ module SubmissionFilter extend ActiveSupport::Concern - include SearchContent + include FederationHelper BROWSE_ATTRIBUTES = ['ontology', 'submissionStatus', 'description', 'pullLocation', 'creationDate', 'contact', 'released', 'naturalLanguage', 'hasOntologyLanguage', @@ -21,17 +21,19 @@ def submissions_paginate_filter(params) filter_params = params.permit(@filters.keys).to_h init_filters(params) - @analytics = Rails.cache.fetch("ontologies_analytics-#{Time.now.year}-#{Time.now.month}") do + @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) + @ontologies, @errors = @ontologies.partition { |x| !x.errors } + # get fair scores of all ontologies @fair_scores = fairness_service_enabled? ? get_fair_score('all') : nil @total_ontologies = @ontologies.size - search_backend = params[:search_backend] + params = { query: @search, status: request_params[:status], show_views: @show_views, @@ -43,16 +45,12 @@ def submissions_paginate_filter(params) groups: request_params[:group], categories: request_params[:hasDomain], formats: request_params[:hasOntologyLanguage] } + submissions = filter_submissions(@ontologies, **params) - if search_backend.eql?('index') - submissions = filter_using_index(**params) - submissions = @ontologies.map{ |ont| ontology_hash(ont, submissions) } - else - submissions = filter_using_data(@ontologies, **params) - end + submissions = merge_by_acronym(submissions) - submissions = sort_submission_by(submissions, @sort_by, @search) + submissions = sort_submission_by(submissions, @sort_by, @search) @page = paginate_submissions(submissions, request_params[:page].to_i, request_params[:pagesize].to_i) @@ -67,49 +65,51 @@ def ontologies_with_filters_url(filters, page: 1, count: false) private - def filter_using_index(query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) - search_ontologies( - query: query, - status: status, - show_views: show_views, - private_only: private_only, - languages: languages, - page_size: page_size, - formality_level: formality_level, - is_of_type: is_of_type, - groups: groups, categories: categories, - formats: formats - ) + 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 = ontologies.select { |x| helpers.internal_ontology?(x[:id]) }.first || ontologies.first + end + ontology[:sources] = ontologies.map { |x| x[:id] } + merged_submissions << ontology + end + merged_submissions end - def filter_using_data(ontologies, query:, status:, show_views:, private_only:, languages:, page_size:, formality_level:, is_of_type:, groups:, categories:, formats:) + 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) + + submissions = submissions.map { |x| x[:ontology] ? [x[:ontology][:id], x] : nil }.compact.to_h + 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 @@ -131,7 +131,7 @@ def paginate_submissions(all_submissions, page, size) end def sort_submission_by(submissions, sort_by, query = nil) - return submissions.sort_by { |x| x[:rank] ? -x[:rank] : 0} unless query.blank? + 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) } @@ -209,13 +209,17 @@ def filters_params(params, includes: BROWSE_ATTRIBUTES.join(','), page: 1, pages @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 def ontology_hash(ont, submissions) o = {} - sub = submissions.select{|x| x.ontology&.id.eql?(ont.id)}.first + sub = submissions[ont.id] o[:ontology] = ont @@ -225,7 +229,7 @@ def ontology_hash(ont, submissions) o[:hasOntologyLanguage] = sub&.hasOntologyLanguage - if sub&.metrics + if sub&.metrics && !sub.metrics.is_a?(String) o[:class_count] = sub.metrics.classes o[:individual_count] = sub.metrics.individuals else @@ -237,10 +241,10 @@ def ontology_hash(ont, submissions) o[:note_count] = ont.notes&.length || 0 o[:project_count] = ont.projects&.length || 0 - o[:popularity] = @analytics[ont.acronym] || 0 - o[:rank] = sub&[:rank] || 0 + o[:popularity] = @analytics[ont.id.split('/').last.to_s] || 0 + o[:rank] = sub ? sub[:rank] : 0 - OpenStruct.new(o) + o end def add_submission_attributes(ont_hash, sub) @@ -346,10 +350,12 @@ def check_id(name_value, objects, name_key) def object_filter(objects, object_name, name_key = 'acronym') checks = params[object_name]&.split(',') || [] - checks = checks.map { |x| check_id(x, objects, name_key) }.compact + 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 @@ -370,7 +376,7 @@ def count_objects(ontologies) object_names.each do |name| values = Array(ontology[name]) values.each do |v| - v.gsub!('http://data.bioontology.org', rest_url) + v = helpers.link_last_part(v) objects_count[name] = {} unless objects_count[name] objects_count[name][v] = (objects_count[name][v] || 0) + 1 diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb index a8c744fe4..11d0ae2ca 100644 --- a/app/controllers/ontologies_controller.rb +++ b/app/controllers/ontologies_controller.rb @@ -26,6 +26,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" @@ -49,8 +51,8 @@ def ontologies_filter 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}_#{value}") do - helpers.turbo_frame_tag("count_#{section}_#{value}") do + 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 @@ -568,4 +570,7 @@ def search_first_instance_id return !results.blank? ? results.first[:name] : nil end + def set_federated_portals + RequestStore.store[:federated_portals] = params[:portals]&.split(',') + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c9fb87375..c134d5d96 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -41,7 +41,7 @@ def resolve_namespaces def ontologies_analytics begin data = LinkedData::Client::Analytics.last_month.onts - data.map{|x| [x[:ont].to_s, x[:views]]}.to_h + data.map{|x| [x[:ont].split('/').last.to_s, x[:views]]}.to_h rescue StandardError {} end diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb new file mode 100644 index 000000000..afd60dfa0 --- /dev/null +++ b/app/helpers/federation_helper.rb @@ -0,0 +1,97 @@ +module FederationHelper + + def federated_portals + $FEDERATED_PORTALS ||= LinkedData::Client.settings.federated_portals + end + + def internal_portal_config(id) + return unless internal_ontology?(id) + + { + name: portal_name, + api: rest_url, + apikey: $API_KEY, + ui: $UI_URL, + color: "var(--primary-color)", + 'light-color': 'var(--light-color)', + } + 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 nil 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?(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/app/views/home/index.html.haml b/app/views/home/index.html.haml index 83fc78158..be0075bfe 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -214,10 +214,10 @@ .home-support-items - $PORTALS_INSTANCES&.each do |portal| %div.text-center - = link_to portal[:link], target: '_blank', class: 'home-logo-instances', style: "background-color: #{portal[:color]}" do + = link_to portal[:ui], target: '_blank', class: 'home-logo-instances', style: "background-color: #{portal[:color]}" do = inline_svg 'logo-white.svg', width: "35", height: "26" %p{style: "color: #{portal[:color]}"} - = portal[:portal] + = portal[:name] .home-section .home-section-title diff --git a/app/views/home/tools.html.haml b/app/views/home/tools.html.haml index 941d60ec7..d4f0455b6 100644 --- a/app/views/home/tools.html.haml +++ b/app/views/home/tools.html.haml @@ -3,10 +3,9 @@ = link_to tool[:link], class: "mx-2 d-block", style: "width: 35%" do = render Layout::CardComponent.new do |c| - c.header do |h| - - h.text do - %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] + %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/browser/_ontologies.html.haml b/app/views/ontologies/browser/_ontologies.html.haml index bd3aed0cf..3ca8685bc 100644 --- a/app/views/ontologies/browser/_ontologies.html.haml +++ b/app/views/ontologies/browser/_ontologies.html.haml @@ -4,11 +4,23 @@ 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;") { "#{t("ontologies.showing_ontologies_size", ontologies_size: @count, analytics_size: @total_ontologies)} (#{sprintf("%.2f", @time)}s)" } - + = 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| - = 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/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml index d1979fd35..fd9777e3a 100644 --- a/app/views/ontologies/browser/browse.html.haml +++ b/app/views/ontologies/browser/browse.html.haml @@ -65,12 +65,17 @@ .browse-filter-checks-container - 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"]) || values[1]&.include?(object["value"]) , title: title) do |c| + = group_chip_component(name: key, object: object, checked: values[1].any?(link_last_part(object["id"])) || values[1].any?(link_last_part(object["value"])) , 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) + %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 diff --git a/app/views/ontologies/sections/_metadata.html.haml b/app/views/ontologies/sections/_metadata.html.haml index 69aadbc57..be2083e5c 100755 --- a/app/views/ontologies/sections/_metadata.html.haml +++ b/app/views/ontologies/sections/_metadata.html.haml @@ -22,7 +22,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) @@ -51,11 +51,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 @@ -64,9 +63,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' diff --git a/config/bioportal_config_env.rb.sample b/config/bioportal_config_env.rb.sample index 277b97201..352805b19 100644 --- a/config/bioportal_config_env.rb.sample +++ b/config/bioportal_config_env.rb.sample @@ -1,3 +1,6 @@ +$DEBUG_API_CLIENT = false +$API_CLIENT_INVALIDATE_CACHE = false + # Organization info $ORG = ENV['ORG'] $ORG_URL = ENV['ORG_URL'] @@ -37,9 +40,6 @@ $NCBO_API_KEY = ENV['NCBO_API_KEY'] $FAIRNESS_DISABLED = ENV['FAIRNESS_DISABLED'] $FAIRNESS_URL = ENV['FAIRNESS_URL'] - - - # 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" @@ -211,53 +211,71 @@ $HOME_PAGE_LOGOS = [ } ] + +# Federation configuration $PORTALS_INSTANCES = [ { - color: '#31b403', - portal: 'AgroPortal', - link: 'https://agroportal.lirmm.fr/' + name: 'AgroPortal', + api: 'https://data.agroportal.lirmm.fr', + ui: 'https://agroportal.lirmm.fr/', + color: '#349696', + apikey: '1de0a270-29c5-4dda-b043-7c3580628cd5', + 'light-color': '#F1F6FA', }, { + name: 'BioPortal', + ui: 'https://bioportal.bioontology.org/', + api: 'https://data.bioontology.org/', + apikey: '8b5b7825-538d-40e0-9e9e-5ab9274a9aeb', color: '#234979', - portal: 'BioPortal', - link: 'https://bioportal.bioontology.org/' + 'light-color': '#E9F2FA', }, { + name: 'SIFR BioPortal', + ui: 'https://bioportal.lirmm.fr/', color: '#74a9cb', - portal: 'SIFR BioPortal', - link: 'https://bioportal.lirmm.fr/' }, { + name: 'EcoPortal', + ui: 'https://ecoportal.lifewatch.eu/', + api: 'https://data.ecoportal.lifewatch.eu/', + apikey: "43a437ba-a437-4bf0-affd-ab520e584719", color: '#0d508a', - portal: 'EcoPortal', - link: 'https://ecoportal.lifewatch.eu/' + 'light-color': '#E9F2FA', }, { + name: 'MedPortal', + ui: 'http://medportal.bmicc.cn/', color: '#234979', - portal: 'MedPortal', - link: 'http://medportal.bmicc.cn/' }, { + name: 'MatPortal', + ui: 'https://matportal.org/', color: '#009574', - portal: 'MatPortal', - link: 'https://matportal.org/' }, { + name: 'IndustryPortal', + ui: 'http://industryportal.enit.fr', color: '#1c0f5d', - portal: 'IndustryPortal', - link: 'http://industryportal.enit.fr' }, { + name: 'EarthPortal', + ui: 'https://earthportal.eu/', + api: 'https://data.earthportal.eu/', + apikey: "c9147279-954f-41bd-b068-da9b0c441288", color: '#1e2251', - portal: 'EarthPortal', - link: 'https://earthportal.eu/' + 'light-color': '#F0F5F6' }, { + name: 'BiodivPortal', + ui: 'https://biodivportal.gfbio.org/', + api: 'https://data.biodivportal.gfbio.org/', + apikey: "47a57aa3-7b54-4f34-b695-dbb5f5b7363e", color: '#33691B', - portal: 'BiodivPortal', - link: 'https://biodivportal.gfbio.org/' + 'light-color': '#EBF5F5', } ] + $ONTOPORTAL_WEBSITE_LINK = "https://ontoportal.org/" $ONTOPORTAL_GITHUB_REPO = "https://github.com/ontoportal" diff --git a/config/initializers/ontologies_api_client.rb b/config/initializers/ontologies_api_client.rb index ca8f07e9e..068da5382 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 = $PORTALS_INSTANCES ? $PORTALS_INSTANCES.map{|x| x[:api] && x[:apikey] ? [x[:name].downcase.to_sym, x] : nil }.compact.to_h : {} end diff --git a/config/locales/en.yml b/config/locales/en.yml index fd04ba863..79ae1583e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1162,7 +1162,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 f537fc23a..f044708cb 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1188,7 +1188,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