diff --git a/Gemfile b/Gemfile
index 8309654a1b..8cc430e0b7 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, used by federated search to rank results
+gem 'string-similarity'
+
# Render SVG files in Rails views
gem 'inline_svg'
@@ -97,9 +100,9 @@ gem 'iso-639', '~> 0.3.6'
gem 'countries', '~> 5.7'
# 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'
gem 'net-ftp', '~> 0.2.0', require: false
gem 'net-http', '~> 0.3.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 97facd1e1b..73328d8412 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: 7423b46ff6fa7e5ef0f1d36548f7c04466939f71
+ 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
@@ -145,20 +147,21 @@ GEM
crass (1.0.6)
css_parser (1.17.1)
addressable
+ csv (3.3.0)
dalli (3.2.8)
date (3.3.4)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
- deepl-rb (2.5.3)
+ deepl-rb (3.0.2)
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)
- execjs (2.9.1)
+ excon (0.112.0)
+ execjs (2.10.0)
faraday (2.0.1)
faraday-net_http (~> 2.0)
ruby2_keywords (>= 0.0.4)
@@ -176,7 +179,7 @@ GEM
sass-rails
globalid (1.2.1)
activesupport (>= 6.1)
- graphql (2.3.14)
+ graphql (2.3.19)
base64
fiber-storage
graphql-client (0.23.0)
@@ -203,7 +206,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)
@@ -217,7 +220,7 @@ GEM
terminal-table (>= 1.5.1)
i18n-tasks-csv (1.1)
i18n-tasks (~> 0.9)
- importmap-rails (2.0.1)
+ importmap-rails (2.0.3)
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0)
@@ -225,10 +228,11 @@ 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)
+ iso-639 (0.3.8)
+ csv
jquery-rails (4.6.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
@@ -237,15 +241,15 @@ GEM
railties (>= 3.2.16)
jsbundling-rails (1.3.1)
railties (>= 6.0.0)
- json (2.7.2)
- json-jwt (1.16.6)
+ json (2.7.4)
+ json-jwt (1.16.7)
activesupport (>= 4.2)
aes_key_wrap
base64
bindata
faraday (~> 2.0)
faraday-follow_redirects
- jwt (2.8.2)
+ jwt (2.9.3)
base64
language_server-protocol (3.17.0.3)
launchy (3.0.1)
@@ -262,7 +266,7 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.1)
- loofah (2.22.0)
+ loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
lookbook (1.5.5)
@@ -287,12 +291,13 @@ 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)
+ msgpack (1.7.3)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.4.1)
@@ -302,7 +307,7 @@ GEM
time
net-http (0.3.2)
uri
- net-imap (0.4.16)
+ net-imap (0.4.17)
date
net-protocol
net-pop (0.1.2)
@@ -315,9 +320,9 @@ 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.13.0)
+ newrelic_rpm (9.14.0)
nio4r (2.7.3)
nokogiri (1.15.6-x86_64-linux)
racc (~> 1.4)
@@ -328,7 +333,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 +343,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,10 +374,10 @@ 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)
+ rack (2.2.10)
rack-protection (3.2.0)
base64 (>= 0.1.0)
rack (~> 2.2, >= 2.2.4)
@@ -422,19 +427,21 @@ 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)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
- rexml (3.3.7)
- rouge (4.3.0)
- rspec-core (3.13.1)
+ rexml (3.3.9)
+ rouge (4.4.0)
+ rspec-core (3.13.2)
rspec-support (~> 3.13.0)
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)
@@ -446,7 +453,7 @@ GEM
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.1)
- rubocop (1.66.1)
+ rubocop (1.67.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
@@ -487,7 +494,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
@@ -500,7 +507,7 @@ GEM
actionpack (>= 6.1)
activesupport (>= 6.1)
sprockets (>= 3.0.0)
- sshkit (1.23.1)
+ sshkit (1.23.2)
base64
net-scp (>= 1.1.2)
net-sftp (>= 2.1.2)
@@ -508,25 +515,25 @@ GEM
ostruct
stimulus-rails (1.3.4)
railties (>= 6.0.0)
+ string-similarity (2.1.0)
stringio (3.1.1)
temple (0.10.3)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
- terser (1.2.3)
+ terser (1.2.4)
execjs (>= 0.3.0, < 3)
thor (1.3.2)
tilt (2.4.0)
time (0.4.0)
date
timeout (0.4.1)
- turbo-rails (2.0.6)
+ turbo-rails (2.0.11)
actionpack (>= 6.0.0)
- activejob (>= 6.0.0)
railties (>= 6.0.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unaccent (0.4.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)
@@ -538,7 +545,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)
@@ -620,6 +627,7 @@ DEPENDENCIES
simplecov-cobertura
sprockets-rails
stimulus-rails
+ string-similarity
terser
turbo-rails
tzinfo-data
diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/stylesheets/application.css.scss.erb
index ac5160afc2..52b88a0a6d 100755
--- a/app/assets/stylesheets/application.css.scss.erb
+++ b/app/assets/stylesheets/application.css.scss.erb
@@ -58,7 +58,9 @@
@import "agent_tooltip";
@import "content_finder";
@import "tools";
+@import "portal_configuration";
@import "taxonomy";
+@import "federation";
/* Bootstrap and Font Awesome */
@import "bootstrap";
diff --git a/app/assets/stylesheets/browse.scss b/app/assets/stylesheets/browse.scss
index 2dc96db265..a55292bde0 100644
--- a/app/assets/stylesheets/browse.scss
+++ b/app/assets/stylesheets/browse.scss
@@ -451,6 +451,25 @@
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/chip_button.scss b/app/assets/stylesheets/components/chip_button.scss
index 1b19a29815..18916b7fec 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/assets/stylesheets/components/chips.scss b/app/assets/stylesheets/components/chips.scss
index ac1a2a939f..13c15630d2 100644
--- a/app/assets/stylesheets/components/chips.scss
+++ b/app/assets/stylesheets/components/chips.scss
@@ -32,7 +32,8 @@
}
.chips-container.disabled div label > span, .chips-container div label span:has(span.disabled){
- background-color: #f8f9fa !important;
+ opacity: 60%;
+ background-color: #f8f9fa !important;
}
.chips-container div label input[type="checkbox"]:checked ~ span{
@@ -42,4 +43,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/assets/stylesheets/components/search_result.scss b/app/assets/stylesheets/components/search_result.scss
index fc99288618..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;
@@ -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/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/assets/stylesheets/mappings.scss b/app/assets/stylesheets/mappings.scss
index 89a7ebffbe..20ddbb6940 100644
--- a/app/assets/stylesheets/mappings.scss
+++ b/app/assets/stylesheets/mappings.scss
@@ -320,6 +320,28 @@ div#map_from_concept_details_table, div#map_to_concept_details_table {
#concept_mappings_table {
width: 100%;
+ word-break: break-word;
+ font-size: 13px;
+}
+.mappings-table-mapping-to{
+ width: 45%;
+}
+.mappings-table-mapping-to .chip-button-component-container{
+ text-wrap: wrap !important;
+}
+.mappings-table-mapping-to a{
+ background-color: unset;
+ line-height: unset;
+ padding: unset;
+ font-weight: unset;
+ font-size: unset;
+}
+.mappings-table-icon{
+ width: 18px;
+ height: 18px;
+}
+.mappings-table-icon path{
+ fill: var(--gray-color);
}
.summary-mappings-tab-table {
@@ -341,4 +363,7 @@ div#map_from_concept_details_table, div#map_to_concept_details_table {
padding: 10px;
outline: none;
margin-left: 0 !important;
+}
+.mappings-table-actions svg path{
+ fill: var(--primary-color);
}
\ No newline at end of file
diff --git a/app/assets/stylesheets/portal_configuration.scss b/app/assets/stylesheets/portal_configuration.scss
new file mode 100644
index 0000000000..0f72b14730
--- /dev/null
+++ b/app/assets/stylesheets/portal_configuration.scss
@@ -0,0 +1,68 @@
+
+.portal-configuration{
+ padding: 10px;
+ .portal-configuration-title{
+ h3{
+ font-size: 20px;
+ font-weight: 600;
+ margin-bottom: 0;
+ }
+ span{
+ font-size: 16px;
+ }
+ }
+
+ h4, .home-section-title .text{
+ font-size: 15px !important;
+ }
+
+ p {
+ font-size: 14px;
+ }
+ .home-support-items div {
+ font-size: 12px;
+ }
+ .home-support-items a img {
+ height: 48px;
+ width: 48px;
+ }
+ .home-logo-instances-small{
+ padding: 7px;
+ margin: 2px 5px !important;
+ border-radius: 50%;
+ display: inline-block;
+ width: 38px;
+ height: 38px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ .portal-config-ontologies{
+ display: flex;
+ align-items: center;
+ margin-top: 2px;
+ }
+ .portal-config-ontologies svg{
+ width: 14px;
+ height: 14px;
+ margin-right: 4px;
+ }
+ .portal-config-ontologies svg path{
+ fill: var(--primary-color);
+ }
+ .portal-description{
+ font-size: 15px;
+ padding: 11px 0;
+ }
+ .portal-config-federated-with{
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-right: 15px;
+ }
+ .portal-config-title-text{
+ font-weight: 500;
+ font-size: 15px !important;
+ }
+}
diff --git a/app/assets/stylesheets/search.scss b/app/assets/stylesheets/search.scss
index e66312f7e2..5dbea30de7 100644
--- a/app/assets/stylesheets/search.scss
+++ b/app/assets/stylesheets/search.scss
@@ -69,6 +69,7 @@
.search-page-advanced-button .text{
margin-left: 10px;
color: var(--primary-color);
+ margin-top: 2px;
}
.search-page-advanced-button .icon svg path{
@@ -76,6 +77,7 @@
}
.search-page-number-of-results{
color: #888888;
+ max-width: 800px;
}
.search-page-result-element{
diff --git a/app/components/chips_component.rb b/app/components/chips_component.rb
index 624681f812..101643a865 100644
--- a/app/components/chips_component.rb
+++ b/app/components/chips_component.rb
@@ -1,16 +1,26 @@
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, loading: false)
@id = id || name
@name = name
@value = value || 'true'
@checked = checked
@label = label || @value
@tooltip = tooltip
+ @disabled = disabled
+ @loading = loading
end
def checked?
@checked
end
-end
\ No newline at end of file
+
+ def disabled_class_name
+ @disabled ? 'disabled' : ''
+ end
+
+ def loading_class_name
+ @loading ? 'loading' : ''
+ end
+end
diff --git a/app/components/chips_component/chips_component.html.haml b/app/components/chips_component/chips_component.html.haml
index f08d5a33dd..74acecd425 100644
--- a/app/components/chips_component/chips_component.html.haml
+++ b/app/components/chips_component/chips_component.html.haml
@@ -1,9 +1,13 @@
-.chips-container{class: @disabled ? 'disabled' : '', 'data-controller': 'tooltip', title: @tooltip}
+.chips-container{class: "#{disabled_class_name} #{loading_class_name}", 'data-controller': 'tooltip', title: @tooltip}
%div
- %label{:for => "chips-#{@id}-check"}
- %input{:id => "chips-#{@id}-check", :name => @name, :type => "checkbox", :value => @value, checked: checked?, disabled: @disabled}
- %span
- = inline_svg_tag 'check.svg', class: 'chips-check-icon'
- %div
- = @label
- = count
+ - if @loading
+ %label
+ %span.skeleton
+ - else
+ %label{:for => "chips-#{@id}-check"}
+ %input{:id => "chips-#{@id}-check", :name => @name, :type => "checkbox", :value => @value, checked: checked?, disabled: @disabled}
+ %span
+ = inline_svg_tag 'check.svg', class: 'chips-check-icon'
+ %div
+ = @label
+ = count
diff --git a/app/components/display/header_component.rb b/app/components/display/header_component.rb
index 76784068e1..48ae752754 100644
--- a/app/components/display/header_component.rb
+++ b/app/components/display/header_component.rb
@@ -4,7 +4,7 @@ class Display::HeaderComponent < ViewComponent::Base
include ComponentsHelper
- renders_one :text
+
def initialize(text: nil, tooltip: nil)
super
@@ -14,7 +14,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/display/search_result_component.rb b/app/components/display/search_result_component.rb
index 18d56a288e..4b112b60f8 100644
--- a/app/components/display/search_result_component.rb
+++ b/app/components/display/search_result_component.rb
@@ -2,17 +2,24 @@ class Display::SearchResultComponent < ViewComponent::Base
include UrlsHelper
include ModalHelper
include MultiLanguagesHelper
+ include FederationHelper
+ include ComponentsHelper
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_id: 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
@link = link
@is_sub_component = is_sub_component
- @ontology_acronym = ontology_acronym
+ @ontology_acronym = ontology_id&.split('/')&.last
@number = number.to_s
+ @portal_name = portal_name
+ @portal_color = portal_color
+ @portal_light_color = portal_light_color
+ @other_portals = other_portals
end
def sub_component_class
@@ -61,12 +68,22 @@ def visualize_button
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
- inline_svg_tag(icon) +
- content_tag(:div, class: 'text') 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, 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
+
+ def external_class?
+ !@portal_name.nil?
+ end
+
+ def all_federated_portals
+ out = Array(@other_portals)
+ out.prepend({name: @portal_name, color: @portal_color, light_color: @portal_light_color, link: @link}) if external_class?
+ out
+ 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 9d4d48bc08..4771756fd2 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,20 +1,25 @@
.search-result-component{class: sub_component_class, 'data-controller': 'reveal-component'}
- %a.title{href: @link}
- = @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 federated-icon-#{@portal_name} #{@portal_color ? '' : 'd-none'}"
+
- if @uri
.uri
= @uri
- if @definition
-
= display_in_multiple_languages(@definition)
.actions
- = details_button
- = visualize_button
- = mappings_button
+ - unless external_class?
+ = 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')
+ - all_federated_portals.each do |p|
+ = 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
@@ -28,4 +33,4 @@
.search-result-sub-components
- reuses.each do |reuse|
.search-result-sub-component
- = reuse
\ No newline at end of file
+ = reuse
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}
diff --git a/app/components/federated_portal_button_component.rb b/app/components/federated_portal_button_component.rb
new file mode 100644
index 0000000000..4d8a1e886c
--- /dev/null
+++ b/app/components/federated_portal_button_component.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class FederatedPortalButtonComponent < ViewComponent::Base
+ attr_reader :name, :tooltip, :link, :color, :light_color
+
+ def initialize(name:, link:, color:, tooltip:, light_color:)
+ @name = name
+ @tooltip = tooltip
+ @link = link
+ @color = color
+ @light_color = light_color
+ end
+end
diff --git a/app/components/federated_portal_button_component/federated_portal_button_component.html.haml b/app/components/federated_portal_button_component/federated_portal_button_component.html.haml
new file mode 100644
index 0000000000..e45a6f0520
--- /dev/null
+++ b/app/components/federated_portal_button_component/federated_portal_button_component.html.haml
@@ -0,0 +1,7 @@
+%span{'data-controller': 'federation-portals-colors',
+ 'data-federation-portals-colors-color-value': color,
+ 'data-federation-portals-colors-portal-name-value': name.downcase}
+%a{ href: link, target: '_blank', 'data-controller' => 'tooltip', title: tooltip, class: 'federation-portal-button button icon-right', style: color ? "background-color: #{light_color} !important" : '' }
+ = inline_svg_tag('logos/ontoportal.svg', class: "federated-icon-#{name.downcase}")
+ %div{ class: 'text', style: color ? "color: #{color} !important" : '' }
+ = name.humanize.gsub("portal", "Portal")
diff --git a/app/components/federated_portal_button_component/federated_portal_button_component_controller.js b/app/components/federated_portal_button_component/federated_portal_button_component_controller.js
new file mode 100644
index 0000000000..dd6d4a708d
--- /dev/null
+++ b/app/components/federated_portal_button_component/federated_portal_button_component_controller.js
@@ -0,0 +1,18 @@
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+ static values = {
+ color: String,
+ portalName: String,
+ }
+ connect() {
+ this.#initIconsStyle()
+ }
+
+ #initIconsStyle(){
+ const style = document.createElement('style');
+ style.innerHTML = `.federated-icon-${this.portalNameValue} path { fill: ${this.colorValue} !important; }\n`;
+ document.head.appendChild(style);
+ }
+
+ }
diff --git a/app/components/link_text_component.rb b/app/components/link_text_component.rb
index 47ea0f6dfc..36dc79ba33 100644
--- a/app/components/link_text_component.rb
+++ b/app/components/link_text_component.rb
@@ -10,7 +10,7 @@ def initialize(text:, icon: nil, target: nil)
end
def call
- svg_icon = !@icon&.empty? ? inline_svg(@icon) : ''
+ svg_icon = !@icon&.empty? ? inline_svg(@icon, width: '14px', height: '14px') : ''
extra_span = @text == t('mappings.upload_mappings') ? '' : "#{svg_icon}"
"#{@text}#{extra_span}".html_safe
end
diff --git a/app/components/ontology_browse_card_component.rb b/app/components/ontology_browse_card_component.rb
index 84251d7f0d..cb40202109 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, ComponentsHelper
- 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])
+ 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 aaf97e85fd..4f02e889f4 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,12 @@
.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, target: external_ontology? ? '_blank' : ''}
= ontology[:name]+" ("+ontology[:acronym]+")"
= private_ontology_icon(ontology[:private])
+ - if external_ontology?
+ %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))
@@ -21,27 +24,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 +60,26 @@
- 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)
+
+
+ - ontology[:sources]&.each do |id|
+ - config = ontology_portal_config(id)&.last || internal_portal_config(id) || {}
+ - unless config.blank?
+ %span{style: "padding: 3px 0; margin-right: 0.25rem;"}
+ = portal_button(name: config[:name], color: config[:color], light_color: config[:"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'}}
@@ -82,4 +97,4 @@
.two
.browse-sket-column-three
.one
- .two
\ No newline at end of file
+ .two
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
diff --git a/app/components/tab_item_component.rb b/app/components/tab_item_component.rb
index 88f11f890a..8d5f25c8e4 100644
--- a/app/components/tab_item_component.rb
+++ b/app/components/tab_item_component.rb
@@ -44,12 +44,8 @@ def page_name
end
def call
- if title && !title.empty?
- link_to(title, @path, id: "#{item_id}_tab", class: "#{active_class} tab-link", 'data-json-link': @json_link)
- else
- link_to(@path, id: "#{item_id}_tab", class: "#{active_class} tab-link", 'data-json-link': @json_link) do
- content
- end
+ link_to(@path, id: "#{item_id}_tab", class: "#{active_class} tab-link", 'data-json-link': @json_link) do
+ (title && !title.empty?) ? title.html_safe : content
end
end
diff --git a/app/components/tree_link_component.rb b/app/components/tree_link_component.rb
index 73da902f20..fd23706d96 100644
--- a/app/components/tree_link_component.rb
+++ b/app/components/tree_link_component.rb
@@ -2,26 +2,19 @@
class TreeLinkComponent < ViewComponent::Base
include MultiLanguagesHelper, ModalHelper, ApplicationHelper
- def initialize(child:, href:, children_href: , selected: false , data: {}, muted: false, target_frame: nil, open_in_modal: false, is_reused: nil)
+
+ def initialize(child:, href:, children_href:, selected: false, data: {}, muted: false, target_frame: nil, open_in_modal: false, is_reused: nil)
+ super
+
@child = child
@active_style = selected ? 'active' : ''
- #@icons = child.relation_icon(node)
@muted_style = muted ? 'text-muted' : ''
@href = href
@children_link = children_href
- label = (@child.prefLabel || @child.label) rescue @child.id
- if label.nil?
- @pref_label_html = link_last_part(child.id)
- else
- pref_label_lang, @pref_label_html = select_language_label(label)
- pref_label_lang = pref_label_lang.to_s.upcase
- @tooltip = pref_label_lang.eql?("@NONE") ? "" : pref_label_lang
- if child.obsolete?
- @pref_label_html = "#{@pref_label_html}".html_safe
- end
- end
- @data ||= { controller: 'tooltip', 'tooltip-position-value': 'right', turbo: true, 'turbo-frame': target_frame, action: 'click->simple-tree#select'}
+ @pref_label_html, @tooltip = node_label(child)
+
+ @data ||= { controller: 'tooltip', 'tooltip-position-value': 'right', turbo: true, 'turbo-frame': target_frame, action: 'click->simple-tree#select' }
@data.merge!(data) do |_, old, new|
"#{old} #{new}"
@@ -32,7 +25,6 @@ def initialize(child:, href:, children_href: , selected: false , data: {}, muted
@is_reused = is_reused
end
-
# This gives a very hacky short code to use to uniquely represent a class
# based on its parent in a tree. Used for unique ids in HTML for the tree view
def short_uuid
@@ -49,7 +41,7 @@ def open?
end
def border_left
- !@child.hasChildren ? 'pl-3 tree-border-left' : ''
+ !@child.hasChildren ? 'pl-3 tree-border-left' : ''
end
def li_id
@@ -75,4 +67,26 @@ def open_children_link
end
+ private
+
+ def node_label(child)
+ label = begin
+ child.prefLabel || child.label
+ rescue
+ child.id
+ end
+
+ if label.nil?
+ pref_label_html = link_last_part(child.id)
+ else
+ pref_label_lang, pref_label_html = select_language_label(label)
+ pref_label_lang = pref_label_lang.to_s.upcase
+ tooltip = pref_label_lang.eql?("@NONE") ? "" : pref_label_lang
+
+ pref_label_html = "#{pref_label_html}".html_safe if child.obsolete?
+ end
+
+ [pref_label_html, tooltip]
+ end
+
end
diff --git a/app/controllers/admin/categories_controller.rb b/app/controllers/admin/categories_controller.rb
index f2c0d3ea00..1e3ba2ed8b 100644
--- a/app/controllers/admin/categories_controller.rb
+++ b/app/controllers/admin/categories_controller.rb
@@ -122,7 +122,7 @@ def unescape_id
end
def category_params
- params.require(:category).permit(:acronym, :name, :description, :parentCategory, {ontologies:[]}).to_h
+ params.require(:category).permit(:acronym, :name, :description, {parentCategory: []}, {ontologies:[]}).to_h
end
def _categories
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index f75a395b75..7af57ee341 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -176,6 +176,10 @@ def rest_url
helpers.rest_url
end
+ def request_portals
+ helpers.request_portals
+ end
+
def parse_response_body(response)
return nil if response.nil?
@@ -383,23 +387,6 @@ def get_apikey()
return apikey
end
- def total_mapping_count
- total_count = 0
-
- begin
- stats = LinkedData::Client::HTTP.get("#{REST_URI}/mappings/statistics/ontologies")
- unless stats.blank?
- stats = stats.to_h.compact
- # Some of the mapping counts are erroneously stored as strings
- stats.transform_values!(&:to_i)
- total_count = stats.values.sum
- end
- rescue
- LOG.add :error, e.message
- end
-
- return total_count
- end
def determine_layout
if Rails.env.appliance?
@@ -437,6 +424,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/collections_controller.rb b/app/controllers/collections_controller.rb
index 31b5db7786..c01c0cf879 100644
--- a/app/controllers/collections_controller.rb
+++ b/app/controllers/collections_controller.rb
@@ -37,9 +37,12 @@ def index
end
def show
- redirect_to(ontology_path(id: params[:ontology_id], p: 'collections', collectionid: params[:id], lang: request_lang)) and return unless turbo_frame_request?
+
+ redirect_to(ontology_path(id: params[:ontology], p: 'collections', collectionid: params[:id], lang: request_lang)) and return unless turbo_frame_request?
@collection = get_request_collection
+
+ render partial: "collections/show"
end
def show_label
diff --git a/app/controllers/concepts_controller.rb b/app/controllers/concepts_controller.rb
index 196a61f97d..1063c953b4 100644
--- a/app/controllers/concepts_controller.rb
+++ b/app/controllers/concepts_controller.rb
@@ -45,14 +45,15 @@ def index
@ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first
@ob_instructions = helpers.ontolobridge_instructions_template(@ontology)
- @submission = @ontology.explore.latest_submission(include: 'all')
+ @submission = @ontology.explore.latest_submission(include:'uriRegexPattern,preferredNamespaceUri')
+
+ @concept = @ontology.explore.single_class({dispay: 'prefLabel'}, params[:id])
- @concept = @ontology.explore.single_class({full: true}, params[:id])
concept_not_found(params[:id]) if @concept.nil?
@schemes = params[:concept_schemes].split(',')
@concept.children = @concept.explore.children(pagesize: 750, concept_schemes: Array(@schemes).join(','), language: request_lang, display: 'prefLabel,obsolete,hasChildren').collection || []
- @concept.children.sort! { |x, y| (x.prefLabel || "").downcase <=> (y.prefLabel || "").downcase } unless @concept.children.empty?
+
render turbo_stream: [
replace(helpers.child_id(@concept) + '_open_link') { TreeLinkComponent.tree_close_icon },
replace(helpers.child_id(@concept) + '_childs') do
diff --git a/app/controllers/concerns/search_aggregator.rb b/app/controllers/concerns/search_aggregator.rb
index d50d369a8f..078c89c5c9 100644
--- a/app/controllers/concerns/search_aggregator.rb
+++ b/app/controllers/concerns/search_aggregator.rb
@@ -1,5 +1,6 @@
module SearchAggregator
- include UrlsHelper, MultiLanguagesHelper
+ include UrlsHelper, MultiLanguagesHelper, FederationHelper
+ require 'string-similarity'
extend ActiveSupport::Concern
BLACKLIST_FIX_STR = [
"https://",
@@ -27,29 +28,43 @@ module SearchAggregator
def aggregate_results(query, results)
ontologies = aggregate_by_ontology(results)
grouped_results = add_subordinate_ontologies(query, ontologies)
+
all_ontologies = LinkedData::Client::Models::Ontology.all(include: 'acronym,name', include_views: true, display_links: false, display_context: false)
- grouped_results.map do |group|
+
+ search_results = grouped_results.map do |group|
format_search_result(group, all_ontologies)
end
+
+ if federated_request?
+ search_results = merge_sort_federated_results(query, search_results)
+ search_results = swap_canonical_portal_results_first(search_results)
+ end
+
+ search_results
end
def format_search_result(result, ontologies)
same_ont = result[:same_ont]
same_cls = result[:sub_ont]
result = same_ont.shift
- ontology = result.links['ontology'].split('/').last
+ ontology = result.links['ontology']
{
- root: search_result_elem(result, ontology, ontology_name_acronym(ontologies, ontology)),
- descendants: same_ont.map { |x| search_result_elem(x, ontology, '') },
- reuses: same_cls.map do |x|
- format_search_result(x, ontologies)
- end
+ root: search_result_elem(result, ontology, ontology_name_acronym(ontologies, ontology)),
+ descendants: same_ont.map { |x| search_result_elem(x, ontology, '') },
+ reuses: same_cls.map do |x|
+ format_search_result(x, ontologies)
+ end
}
end
private
+ def merge_sort_federated_results(query, search_results)
+ search_results = merge_federated_results(search_results)
+ sort_results_by_string_similarity(query, search_results)
+ end
+
def search_concept_label(label)
label = language_hash(label)
@@ -59,22 +74,26 @@ 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)
+ def search_result_elem(class_object, ontology_id, title)
label = search_concept_label(class_object.prefLabel)
-
- {
+ request_lang = helpers.request_lang&.eql?("ALL") ? '' : "&language=#{helpers.request_lang}"
+ ontology_acronym = link_last_part(ontology_id)
+ result = {
uri: class_object.id.to_s,
- title: title.nil? || title.empty? ? "#{label} - #{ontology_acronym}" : "#{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
+ title: title.to_s.empty? ? "#{label} - #{ontology_acronym}" : "#{label} - #{title}",
+ ontology_id: ontology_id,
+ link: "/ontologies/#{ontology_acronym}?p=classes&conceptid=#{escape(class_object.id)}#{request_lang}",
+ definition: class_object.definition,
}
- end
+ result.merge!(class_federation_configuration(class_object)) if federated_request?
+
+ result
+ end
def ontology_name_acronym(ontologies, selected_acronym)
ontology = ontologies.select { |x| x.acronym.eql?(selected_acronym.split('/').last) }.first
@@ -228,5 +247,63 @@ def blacklist_cls_id_components(cls_id, blacklist_words)
stripped_id
end
-end
+ def merge_federated_results(search_results)
+ search_results.each do |element|
+ element[:root][:other_portals] = []
+ 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]
+ 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,
+ ontology_id: reuse[:root][:ontology_id]
+ }
+ true
+ else
+ false
+ end
+ end
+ end
+ end
+
+ 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?
+
+ 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)
+ -root_similarity
+ end
+ end
+end
diff --git a/app/controllers/concerns/submission_filter.rb b/app/controllers/concerns/submission_filter.rb
index 45746e36e2..c039147280 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,22 +45,20 @@ 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) if federation_enabled?
- 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)
count = @page.page.eql?(1) ? count_objects(submissions) : {}
- [@page.collection, @page.totalCount, count, filter_params]
+ federation_counts = federated_browse_counts(submissions)
+
+ [@page.collection, @page.totalCount, count, filter_params, federation_counts]
end
def ontologies_with_filters_url(filters, page: 1, count: false)
@@ -67,49 +67,47 @@ 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|
+ ontology = canonical_ontology(ontologies)
+ 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 +129,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 +207,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 +227,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 +239,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 +348,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 +374,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/home_controller.rb b/app/controllers/home_controller.rb
index 3f75bdd3f6..2d20328b74 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -3,31 +3,15 @@
class HomeController < ApplicationController
layout :determine_layout
-
- include FairScoreHelper
+ include FairScoreHelper, FederationHelper,MetricsHelper
def index
@analytics = helpers.ontologies_analytics
- # Calculate BioPortal summary statistics
-
- @ont_count = if @analytics.empty?
- LinkedData::Client::Models::Ontology.all.size
- else
- @analytics.keys.size
- end
- metrics = LinkedData::Client::Models::Metrics.all
- metrics = metrics.each_with_object(Hash.new(0)) do |h, sum|
- h.to_hash.slice(:classes, :properties, :individuals).each { |k, v| sum[k] += v }
- end
@slices = LinkedData::Client::Models::Slice.all
- @cls_count = metrics[:classes]
- @individuals_count = metrics[:individuals]
- @prop_count = metrics[:properties]
- @map_count = total_mapping_count
- @projects_count = LinkedData::Client::Models::Project.all.length
- @users_count = LinkedData::Client::Models::User.all.length
+ @metrics = portal_metrics(@analytics)
+
@upload_benefits = [
t('home.benefit1'),
@@ -51,6 +35,15 @@ def set_cookies
render 'cookies', layout: nil
end
+ def portal_config
+ @config = $PORTALS_INSTANCES.select { |x| x[:name].downcase.eql?((params[:portal] || helpers.portal_name).downcase) }.first
+ if @config && @config[:api]
+ @portal_config = LinkedData::Client::Models::Ontology.top_level_links(@config[:api]).to_h
+ else
+ @portal_config = {}
+ end
+ end
+
def tools
@tools = {
search: {
@@ -157,6 +150,16 @@ def annotator_recommender_form
end
end
+
+ def federation_portals_status
+ @name = params[:name]
+ @acronym = params[:acronym]
+ @key = params[:portal_name]
+ @checked = params[:checked].eql?('true')
+ @portal_up = federation_portal_status(portal_name: @key.downcase.to_sym)
+ render inline: helpers.federation_chip_component(@key, @name, @acronym, @checked, @portal_up)
+ end
+
private
# Dr. Musen wants 5 specific groups to appear first, sorted by order of importance.
diff --git a/app/controllers/instances_controller.rb b/app/controllers/instances_controller.rb
index 70db773446..acaa005a1f 100644
--- a/app/controllers/instances_controller.rb
+++ b/app/controllers/instances_controller.rb
@@ -41,7 +41,7 @@ def show
redirect_to(ontology_path(id: params[:ontology], p: 'instances', instanceid: params[:id] || params[:instanceid], lang: request_lang)) and return unless turbo_frame_request?
- render partial: 'instances/details', layout: nil
+ render partial: 'show'
end
private
diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb
index b1bcfc8c32..df582acb4e 100644
--- a/app/controllers/mappings_controller.rb
+++ b/app/controllers/mappings_controller.rb
@@ -342,4 +342,56 @@ def valid_values?(values)
end
errors
end
-end
\ No newline at end of file
+
+ def set_mapping_target(concept_to_id:, ontology_to:, mapping_type: )
+ case mapping_type
+ when 'interportal'
+ @map_to_interportal, @map_to_interportal_ontology = ontology_to.match(%r{(.*)/ontologies/(.*)}).to_a[1..]
+ @map_to_interportal_class = concept_to_id
+ when 'external'
+ @map_to_external_ontology = ontology_to
+ @map_to_external_class = concept_to_id
+ else
+ @map_to_bioportal_ontology_id = ontology_to
+ @map_to_bioportal_full_id = concept_to_id
+ end
+ end
+
+ def get_mappings_target_params
+ mapping_type = Array(params[:mapping_type]).first
+ external = true
+ case mapping_type
+ when 'interportal'
+ ontology_to = "#{params[:map_to_interportal]}/ontologies/#{params[:map_to_interportal_ontology]}"
+ concept_to_id = params[:map_to_interportal_class]
+ when 'external'
+ ontology_to = params[:map_to_external_ontology]
+ concept_to_id = params[:map_to_external_class]
+ else
+ ontology_to = params[:map_to_bioportal_ontology_id]
+ concept_to_id = params[:map_to_bioportal_full_id]
+ external = false
+ end
+ [ontology_to, concept_to_id, external]
+ end
+
+ def get_mappings_target
+ ontology_to, concept_to_id, external_mapping = get_mappings_target_params
+ target = ''
+ if external_mapping
+ target_ontology = ontology_to
+ target = concept_to_id
+ else
+ if helpers.link?(ontology_to)
+ target_ontology = LinkedData::Client::Models::Ontology.find(ontology_to)
+ else
+ target_ontology = LinkedData::Client::Models::Ontology.find_by_acronym(ontology_to).first
+ end
+ if target_ontology
+ target = target_ontology.explore.single_class(concept_to_id).id
+ target_ontology = target_ontology.id
+ end
+ end
+ [target_ontology, target, external_mapping]
+ end
+end
diff --git a/app/controllers/ontologies_controller.rb b/app/controllers/ontologies_controller.rb
index a8c744fe4a..041c1e6361 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"
@@ -42,20 +44,33 @@ def index
def ontologies_filter
@time = Benchmark.realtime do
- @ontologies, @count, @count_objects, @request_params = submissions_paginate_filter(params)
+ @ontologies, @count, @count_objects, @request_params, @federation_counts = submissions_paginate_filter(params)
end
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}_#{value}") do
- helpers.turbo_frame_tag("count_#{section}_#{value}") do
- helpers.content_tag(:span, count.to_s, class: "hide-if-loading #{count.zero? ? 'disabled' : ''}")
+ 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.flatten
+
+ unless request_portals.empty?
+ streams += [
+ replace('categories_refresh_for_federation') do
+ key = "categories"
+ objects, checked_values, _ = @filters[key.to_sym]
+ helpers.browse_filter_section_body(checked_values: checked_values,
+ key: key, objects: objects,
+ counts: @count_objects[key.to_sym])
+ end
+ ]
+ end
else
streams = [replace("ontologies_list_view-page-#{@page.page}", partial: 'ontologies/browser/ontologies')]
end
@@ -190,7 +205,8 @@ def instances
def schemes
@schemes = get_schemes(@ontology)
scheme_id = params[:schemeid] || @submission_latest.URI || nil
- @scheme = get_scheme(@ontology, scheme_id) if scheme_id
+ @scheme = scheme_id ? get_scheme(@ontology, scheme_id) : @schemes.first
+
render partial: 'ontologies/sections/schemes', layout: 'ontology_viewer'
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 490072b4bd..5816730466 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -26,13 +26,13 @@ def show
redirect_to projects_path
return
end
-
+
@project = projects.first
@ontologies_used = []
onts_used = @project.ontologyUsed
onts_used.each do |ont_used|
ont = LinkedData::Client::Models::Ontology.find(ont_used)
- unless ont.nil?
+ unless ont.nil? || ont.errors
@ontologies_used << Hash["name", ont.name, "acronym", ont.acronym]
end
end
@@ -62,7 +62,7 @@ def edit
@project = projects.first
@user_select_list = LinkedData::Client::Models::User.all.map {|u| [u.username, u.id]}
@user_select_list.sort! {|a,b| a[1].downcase <=> b[1].downcase}
- @usedOntologies = @project.ontologyUsed || []
+ @usedOntologies = @project.ontologyUsed&.map{|o| o.split('/').last}
@ontologies = LinkedData::Client::Models::Ontology.all
end
@@ -76,7 +76,7 @@ def create
@project = LinkedData::Client::Models::Project.new(values: project_params)
@project_saved = @project.save
-
+
# Project successfully created.
if response_success?(@project_saved)
flash[:notice] = t('projects.project_successfully_created')
@@ -160,10 +160,10 @@ def destroy
def project_params
p = params.require(:project).permit(:name, :acronym, :institution, :contacts, { creator:[] }, :homePage,
:description, { ontologyUsed:[] })
-
+
p[:creator]&.reject!(&:blank?)
- p[:ontologyUsed]&.reject!(&:blank?)
- p.to_h
+ p[:ontologyUsed] ||= []
+ p = p.to_h
end
def flash_error(msg)
diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb
index 7fbbcc973e..8c5c97f1f0 100644
--- a/app/controllers/schemes_controller.rb
+++ b/app/controllers/schemes_controller.rb
@@ -32,9 +32,11 @@ def index
end
def show
- redirect_to(ontology_path(id: params[:ontology_id], p: 'schemes', schemeid: params[:id],lang: request_lang)) and return unless turbo_frame_request?
+ redirect_to(ontology_path(id: params[:ontology], p: 'schemes', schemeid: params[:id],lang: request_lang)) and return unless turbo_frame_request?
@scheme = get_request_scheme
+
+ render partial: "schemes/show"
end
def show_label
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 841f0f948a..e0d15048da 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,7 +1,7 @@
require 'uri'
class SearchController < ApplicationController
- include SearchAggregator, SearchContent
+ include SearchAggregator, SearchContent, FederationHelper
skip_before_action :verify_authenticity_token
@@ -13,14 +13,26 @@ 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?
params[:pagesize] = "150"
- results = LinkedData::Client::Models::Class.search(@search_query, params).collection
+ set_federated_portals
+
+ params[:ontologies] = nil if federated_request?
+
+ @time = Benchmark.realtime do
+ results = LinkedData::Client::Models::Class.search(@search_query, params)
+ @federation_errors = federation_error(results) if federation_error?(results)
+ results = results.collection
+
+
+ @search_results = aggregate_results(@search_query, results)
+ @federation_counts = federated_search_counts(@search_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
@@ -36,6 +48,7 @@ def json_search
params.delete("ontologies")
end
search_page = LinkedData::Client::Models::Class.search(params[:q], params)
+
@results = search_page.collection
response = ""
@@ -141,7 +154,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/controllers/taxonomy_controller.rb b/app/controllers/taxonomy_controller.rb
index 06832abe8b..b3afa2542a 100644
--- a/app/controllers/taxonomy_controller.rb
+++ b/app/controllers/taxonomy_controller.rb
@@ -8,7 +8,6 @@ def index
end
private
-
def initialize_taxonomy
@groups = LinkedData::Client::Models::Group.all
slices = LinkedData::Client::Models::Slice.all
@@ -33,13 +32,13 @@ def nest_categories_children(categories)
category_index[category[:id]] = category
end
categories.each do |category|
- if category.parentCategory
- parent = category_index[category.parentCategory]
+ category[:parentCategory].each do |parent_id|
+ parent = category_index[parent_id]
parent[:children] ||= []
parent[:children] << category
end
end
- categories.reject! { |category| category.parentCategory }
+ categories.reject! { |category| category[:parentCategory]&.any? }
categories
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index c9fb873758..6d565bdb22 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -11,6 +11,7 @@ module ApplicationHelper
include ModalHelper, MultiLanguagesHelper, UrlsHelper
+
RESOLVE_NAMESPACE = {:omv => "http://omv.ontoware.org/2005/05/ontology#", :skos => "http://www.w3.org/2004/02/skos/core#", :owl => "http://www.w3.org/2002/07/owl#",
:rdf => "http://www.w3.org/1999/02/22-rdf-syntax-ns#", :rdfs => "http://www.w3.org/2000/01/rdf-schema#", :metadata => "http://data.bioontology.org/metadata/",
:metadata_def => "http://data.bioontology.org/metadata/def/", :dc => "http://purl.org/dc/elements/1.1/", :xsd => "http://www.w3.org/2001/XMLSchema#",
@@ -23,17 +24,16 @@ module ApplicationHelper
:oboInOwl => "http://www.geneontology.org/formats/oboInOwl#", :idot => "http://identifiers.org/idot/", :sd => "http://www.w3.org/ns/sparql-service-description#",
:cclicense => "http://creativecommons.org/licenses/",
'skos-xl' => "http://www.w3.org/2008/05/skos-xl#"}
- def url_to_endpoint(url)
- uri = URI.parse(url)
- endpoint = uri.path.sub(/^\//, '')
- endpoint
- end
def search_json_link(link = @json_url, style: '')
custom_style = "font-size: 50px; line-height: 0.5; margin-left: 6px; #{style}".strip
render IconWithTooltipComponent.new(icon: "json.svg",link: link, target: '_blank', title: t('fair_score.go_to_api'), size:'small', style: custom_style)
end
+ def portal_name_from_uri(uri)
+ URI.parse(uri).hostname.split('.').first
+ end
+
def resolve_namespaces
RESOLVE_NAMESPACE
end
@@ -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
@@ -55,18 +55,6 @@ def get_apikey
end
end
- def rest_hostname
- extract_hostname(REST_URI)
- end
-
- def extract_hostname(url)
- begin
- uri = URI.parse(url)
- uri.hostname
- rescue URI::InvalidURIError
- url
- end
- end
def omniauth_providers_info
$OMNIAUTH_PROVIDERS
@@ -80,11 +68,6 @@ def omniauth_token_provider(strategy)
omniauth_provider_info(strategy.to_sym).keys.first
end
- def encode_param(string)
- CGI.escape(string)
- end
-
-
def current_user
session[:user]
end
@@ -98,9 +81,6 @@ def child_id(child)
child.id.to_s.split('/').last
end
-
-
- # Create a popup button with a ? inside to display help when hovered
def help_tooltip(content, html_attribs = {}, icon = 'fas fa-question-circle', css_class = nil, text = nil)
html_attribs["title"] = content
attribs = []
@@ -126,36 +106,29 @@ def error_message_alert
end
end
- def onts_for_select
- ontologies ||= LinkedData::Client::Models::Ontology.all(include: "acronym,name")
+ def onts_for_select(include_views: false)
+ ontologies ||= LinkedData::Client::Models::Ontology.all({include: "acronym,name,viewOf", include_views: include_views})
onts_for_select = [['', '']]
ontologies.each do |ont|
next if ( ont.acronym.nil? or ont.acronym.empty? )
acronym = ont.acronym
name = ont.name
abbreviation = acronym.empty? ? "" : "(#{acronym})"
- ont_label = "#{name.strip} #{abbreviation}"
+ ont_label = "#{name.strip} #{abbreviation}#{ont.viewOf ? ' [view]' : ''}"
onts_for_select << [ont_label, acronym]
end
onts_for_select.sort! { |a,b| a[0].downcase <=> b[0].downcase }
onts_for_select
end
- def link_last_part(url)
- return "" if url.nil?
-
- if url.include?('#')
- url.split('#').last
- else
- url.split('/').last
- end
+ def slices_enabled?
+ $ENABLE_SLICES.eql?(true)
end
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"
@@ -183,14 +156,6 @@ def add_proposal_button(parent_id, parent_type)
end
- def link?(str)
- # Regular expression to match strings starting with "http://" or "https://"
- link_pattern = /\Ahttps?:\/\//
- str = str&.strip
- # Check if the string matches the pattern
- !!(str =~ link_pattern)
- end
-
def subscribe_button(ontology_id)
return if ontology_id.nil?
render TurboFrameComponent.new(id: 'subscribe_button', src: ontology_subscriptions_path(ontology_id: ontology_id.split('/').last), class: 'ml-1') do |t|
@@ -360,10 +325,6 @@ def help_path(anchor: nil)
"#{Rails.configuration.settings.links[:help]}##{anchor}"
end
- def uri?(url)
- url =~ /\A#{URI::DEFAULT_PARSER.make_regexp(%w[http https])}\z/
- end
-
def extract_label_from(uri)
label = uri.to_s.chomp('/').chomp('#')
index = label.index('#')
@@ -414,6 +375,11 @@ def portal_name
$SITE
end
+ def current_slice_name
+ name = @subdomain_filter[:name]
+ name.blank? ? nil : name
+ end
+
def navitems
items = [["/ontologies", t('layout.header.browse')],
["/mappings", t('layout.header.mappings')],
@@ -503,7 +469,7 @@ def insert_sample_text_button(text)
end
end
- def empty_state(text)
+ def empty_state(text = t('no_result_was_found'))
content_tag(:div, class:'browse-empty-illustration') do
inline_svg_tag('empty-box.svg') +
content_tag(:p, text)
@@ -546,6 +512,7 @@ def cancel_button_component(class_name: nil, id: , value:, data: nil)
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)
+ render Input::SelectComponent.new(id: id, name: name, value: categories_for_select, selected: selected, multiple: true)
end
+
end
diff --git a/app/helpers/collections_helper.rb b/app/helpers/collections_helper.rb
index a577f4190d..9a80d24111 100644
--- a/app/helpers/collections_helper.rb
+++ b/app/helpers/collections_helper.rb
@@ -1,10 +1,11 @@
module CollectionsHelper
+ include MultiLanguagesHelper
def get_collections(ontology, add_colors: false)
collections = ontology.explore.collections(language: request_lang)
generate_collections_colors(collections) if add_colors
- collections.sort_by{ |x| helpers.main_language_label(x.prefLabel) }
+ collections.sort_by{ |x| main_language_label(x.prefLabel) || '' } if collections
end
def get_collection(ontology, collection_uri)
diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb
index 210e4ab57c..a63ba8edb5 100644
--- a/app/helpers/components_helper.rb
+++ b/app/helpers/components_helper.rb
@@ -1,23 +1,41 @@
module ComponentsHelper
include TermsReuses
- def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, &block)
+ def dropdown_component(id: ,title: nil, tooltip:nil , is_open: false, &block)
+ render DropdownContainerComponent.new(id: id, title: title, tooltip: tooltip, is_open: is_open) do |d|
+ capture(d, &block) if block_given?
+ end
+ end
+
+ def portal_button(name: nil , color: nil , light_color: nil, link: nil, tooltip: nil)
+ render FederatedPortalButtonComponent.new(name: name, color: color, link: link, tooltip: tooltip, light_color: light_color)
+ end
+
+ def tab_item_component(container_tabs:, title:, path:, selected: false, json_link: "", &content)
+ container_tabs.item(title: title.html_safe, path: path, selected: selected, json_link: json_link)
+ container_tabs.item_content { capture(&content) }
+ end
+
+ 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)
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
-
def rdf_highlighter_container(format, content)
render Display::RdfHighlighterComponent.new(format: format, text: content)
end
@@ -29,7 +47,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 +81,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|
@@ -88,7 +106,6 @@ def paginated_list_component(id:, results:, next_page_url:, child_url:, child_tu
end
end
-
def resolvability_check_tag(url)
content_tag(:span, check_resolvability_container(url), style: 'display: inline-block;', onClick: "window.open('#{check_resolvability_url(url: url)}', '_blank');")
end
@@ -103,8 +120,7 @@ def copy_link_to_clipboard(url, show_content: false)
end
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)
@@ -119,11 +135,10 @@ def htaccess_tag(acronym)
end
end
-
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 +151,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
@@ -170,8 +185,8 @@ def chart_component(title: '', type:, labels:, datasets:, index_axis: 'x', show_
content_tag(:canvas, nil, data: data)
end
- def loader_component(type = 'pulsing')
- render LoaderComponent.new(type: type)
+ def loader_component(type:'pulsing', small: false )
+ render LoaderComponent.new(type: type, small: small)
end
def info_tooltip(text, interactive: true)
@@ -255,14 +270,12 @@ def properties_dropdown(id, title, tooltip, properties, is_open: false, &block)
end
end
-
def regular_button(id, value, variant: "secondary", state: "regular", size: "slim", &block)
render Buttons::RegularButtonComponent.new(id:id, value: value, variant: variant, state: state, size: size) do |btn|
capture(btn, &block) if block_given?
end
end
-
def form_save_button
render Buttons::RegularButtonComponent.new(id: 'save-button', value: t('components.save_button'), variant: "primary", size: "slim", type: "submit") do |btn|
btn.icon_left do
@@ -279,5 +292,9 @@ def form_cancel_button
end
end
-
+ def text_with_icon(text:, icon:)
+ content_tag(:div, class: 'd-flex align-items-center icon') do
+ inline_svg_tag(icon, height: '18', weight: '18') + content_tag(:div, class: 'text') {text}
+ end
+ end
end
diff --git a/app/helpers/federation_helper.rb b/app/helpers/federation_helper.rb
new file mode 100644
index 0000000000..d961712bc0
--- /dev/null
+++ b/app/helpers/federation_helper.rb
@@ -0,0 +1,256 @@
+module FederationHelper
+ include ApplicationHelper
+
+ 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) : nil
+ 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(counts, time)
+ output = 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)} (#{counts[federated_portal_name(name).downcase]})", style: color ? "color: #{color}" : "", class: color ? "" : "text-primary")
+ end.compact.join(", ")
+
+ "#{output} in #{sprintf("%.2f", time)}s"
+ end
+
+ def federated_request?
+ params[:portals]
+ end
+
+ def federation_enabled?
+ !federated_portals.blank?
+ end
+
+
+ def federation_error?(response)
+ !response[:errors].blank?
+ end
+
+ def federation_error(response)
+ federation_errors = response[:errors].map { |e| ontology_portal_name(e.split(' ').last.gsub('search', '')) }
+ federation_errors.map { |p| "#{p} #{t('federation.not_responding')} " }.join(' ')
+ end
+
+ def alert_message_if_federation_error(errors, &block)
+ return if errors.blank?
+
+ content_tag(:div, class: 'my-1') do
+ render Display::AlertComponent.new(type: 'warning') do
+ capture(&block)
+ end
+ end
+ end
+
+ def class_federation_configuration(class_object)
+ is_external = federation_external_class?(class_object)
+ portal_name = is_external ? helpers.portal_name_from_uri(class_object.links['ui']) : nil
+
+ result = {
+ 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
+ }
+ result[:link] = class_object.links['ui'] if is_external
+ result
+ end
+
+ 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 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)
+ return false unless portal_api
+ portal_up = false
+ begin
+ response = Faraday.new(url: portal_api) do |f|
+ f.adapter Faraday.default_adapter
+ f.request :url_encoded
+ 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
+
+ def federation_chip_component(key, name, acronym, checked, portal_up)
+ render TurboFrameComponent.new(id:"federation_portals_status_#{key}") do
+ content_tag(:div, style: "cursor: default;") do
+ title = "#{!portal_up ? "#{key.humanize.gsub('portal', 'Portal')} #{t('federation.not_responding')}" : ''}"
+ group_chip_component(name: name,
+ object: { "acronym" => acronym, "value" => key },
+ checked: checked,
+ title: title ,
+ disabled: !portal_up)
+ end
+ end
+ end
+
+ def federation_input_chips(name: nil)
+ federated_portals.map do |key, config|
+ turbo_frame_component = TurboFrameComponent.new(
+ id: "federation_portals_status_#{key}",
+ src: "/status/#{key}?name=#{name}&acronym=#{config[:name]}&checked=#{request_portals.include?(key.to_s)}"
+ )
+
+ content_tag :div do
+ render(turbo_frame_component) do |container|
+ container.loader do
+ render ChipsComponent.new(name: '', loading: true, tooltip: t('federation.check_status', portal: key.to_s.humanize.gsub('portal', 'Portal')))
+ end
+ end
+ end
+ end.join.html_safe
+ end
+
+ def init_federation_portals_status
+ content_tag(:div, class: 'd-none') do
+ federation_input_chips
+ end
+ end
+
+ def federated_search_counts(search_results)
+ ids = search_results.map do |result|
+ result.dig(:root, :ontology_id) || rest_url
+ end
+ counts_ontology_ids_by_portal_name(ids)
+ end
+
+ def federated_browse_counts(ontologies)
+ ids = ontologies.map { |ontology| ontology[:id] }
+ counts_ontology_ids_by_portal_name(ids)
+ end
+
+ private
+
+ def counts_ontology_ids_by_portal_name(portals_ids)
+ counts = Hash.new(0)
+ current_portal, *federation_portals = request_portals
+ portals_ids.each do |id|
+ counts[current_portal.downcase] += 1 if id.include?(current_portal.to_s.downcase)
+
+ federation_portals.each do |portal|
+ portal_api = federated_portals[portal.downcase.to_sym][:api]
+ counts[portal.downcase] += 1 if id.include?(portal_api)
+ end
+ end
+
+ 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
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
index 5df6609674..2ddcdc5275 100644
--- a/app/helpers/home_helper.rb
+++ b/app/helpers/home_helper.rb
@@ -22,6 +22,22 @@ def format_number_abbreviated(number)
end
end
+ def portal_config_tooltip(portal_name, &block)
+ portal_id = portal_name&.downcase
+ title = if federation_portal_status(portal_name: portal_id)
+ render(
+ TurboFrameComponent.new(
+ id: "portal_config_tooltip_#{portal_id}",
+ src: "/config?portal=#{portal_id}",
+ style: "width: 600px !important; max-height: 300px; overflow: scroll"
+ )
+ )
+ end
+ render Display::InfoTooltipComponent.new(text: title, interactive: true) do
+ capture(&block)
+ end
+ end
+
def discover_ontologies_button
render Buttons::RegularButtonComponent.new(id: 'discover-ontologies-button', value: t('home.discover_ontologies_button'), variant: "secondary", state: "regular", href: "/ontologies") do |btn|
btn.icon_right do
@@ -36,4 +52,5 @@ def home_ontoportal_description
content_tag(:div, t('home.ontoportal_description', ontoportal_link: ontoportal_link, github_link: github_link).html_safe, style: "margin-bottom: 20px")
end
+
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
diff --git a/app/helpers/instances_helper.rb b/app/helpers/instances_helper.rb
index db5170b233..79e5b17586 100644
--- a/app/helpers/instances_helper.rb
+++ b/app/helpers/instances_helper.rb
@@ -57,7 +57,7 @@ def link_to_property(property, ontology_acronym)
end
def instance_property_value(property, ontology_acronym)
- if uri?(property)
+ if link?(property)
instance, types = get_instance_and_type(property, ontology_acronym)
return link_to_instance(instance, ontology_acronym) unless instance.empty?
end
diff --git a/app/helpers/mappings_helper.rb b/app/helpers/mappings_helper.rb
index afc85907a8..c1e503b547 100644
--- a/app/helpers/mappings_helper.rb
+++ b/app/helpers/mappings_helper.rb
@@ -2,43 +2,85 @@ module MappingsHelper
# Used to replace the full URI by the prefixed URI
RELATIONSHIP_PREFIX = {
- "http://www.w3.org/2004/02/skos/core#" => "skos:",
- "http://www.w3.org/2000/01/rdf-schema#" => "rdfs:",
- "http://www.w3.org/2002/07/owl#" => "owl:",
- "http://www.w3.org/1999/02/22-rdf-syntax-ns#" => "rdf:",
- "http://purl.org/linguistics/gold/" => "gold:",
- "http://lemon-model.net/lemon#" => "lemon:"
+ 'http://www.w3.org/2004/02/skos/core#' => 'skos:',
+ 'http://www.w3.org/2000/01/rdf-schema#' => 'rdfs:',
+ 'http://www.w3.org/2002/07/owl#' => 'owl:',
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' => 'rdf:',
+ 'http://purl.org/linguistics/gold/' => 'gold:',
+ 'http://lemon-model.net/lemon#' => 'lemon:'
}
INTERPORTAL_HASH = $INTERPORTAL_HASH
-
- # a little method that returns true if the URIs array contain a gold:translation or gold:freeTranslation
- def translation?(relation_array)
- if relation_array.kind_of?(Array)
- relation_array.map!(&:downcase)
- if relation_array.include? "http://purl.org/linguistics/gold/translation"
- true
- elsif relation_array.include? "http://purl.org/linguistics/gold/freetranslation"
- true
- else
- false
+ def mapping_links(mapping, concept)
+ target_concept = mapping.classes.select do |c|
+ c.id != concept.id && c.links['ontology'] != concept.links['ontology']
+ end.first
+ target_concept ||= mapping.classes.last
+ process = mapping.process || {}
+
+ if inter_portal_mapping?(target_concept)
+ cls_link = ajax_to_inter_portal_cls(target_concept)
+ ont_name = target_concept.links['ontology']
+ ont_link = link_to ont_name, get_inter_portal_ui_link(ont_name, process['name']), target: '_blank'
+ source_tooltip = 'Internal-portal'
+ elsif internal_mapping?(target_concept)
+ begin
+ ont = target_concept.explore.ontology
+ ont_name = ont.acronym
+ ont_link = link_to ont_name, ontology_path(ont_name), 'data-turbo-frame': '_top'
+ rescue
+ ont_name = target_concept.links['ontology'] || target_concept.id
+ ont_link = ont_name
end
+ cls_link = raw(get_link_for_cls_ajax(target_concept.id, ont_name, '_top'))
+ source_tooltip = 'Internal'
else
- false
+ cls_label = ExternalLinkTextComponent.new(text: target_concept.links['self']).call
+ cls_link = raw("#{cls_label}")
+ ont_name = target_concept.links['ontology']
+ ont_link = link_to ExternalLinkTextComponent.new(text: ont_name).call, target_concept.links['ontology'],
+ target: '_blank'
+ source_tooltip = 'External'
end
+
+ [cls_link, ont_link, source_tooltip]
+ end
+
+ def mapping_prefixed_relations(mapping)
+ process = mapping.process || {}
+ Array(process[:relation]).each { |relation| get_prefixed_uri(relation) }
+ end
+
+ def mapping_type_tooltip(map)
+ relations = mapping_prefixed_relations(map)
+ process = map.process || {}
+ type = if map.source.to_s.include? 'SKOS'
+ 'SKOS'
+ else
+ map.source
+ end
+ types_description = {
+ 'CUI' => t('mappings.types_description.cui'),
+ 'LOOM' => t('mappings.types_description.loom'),
+ 'REST' => t('mappings.types_description.rest'),
+ 'SAME_URI' => t('mappings.types_description.same_uri'),
+ 'SKOS' => t('mappings.types_description.skos')
+ }
+ type_tooltip = content_tag(:div, "#{map.source} #{relations.join(', ')} : #{types_description[type]} #{process[:source_name]}".strip, style: 'width: 300px')
+ [type, type_tooltip]
end
# a little method that returns the uri with a prefix : http://purl.org/linguistics/gold/translation become gold:translation
def get_prefixed_uri(uri)
RELATIONSHIP_PREFIX.each { |k, v| uri.sub!(k, v) }
- return uri
+ uri
end
# method to get (using http) prefLabel for interportal classes
# Using bp_ajax_controller.ajax_process_interportal_cls will try to resolve class labels.
def ajax_to_inter_portal_cls(cls)
- inter_portal_acronym = get_inter_portal_acronym(cls.links["ui"])
+ inter_portal_acronym = get_inter_portal_acronym(cls.links['ui'])
href_cls = " href='#{cls.links["ui"]}' "
if inter_portal_acronym
data_cls = " data-cls='#{cls.links["self"]}?apikey=' "
@@ -52,7 +94,7 @@ def ajax_to_inter_portal_cls(cls)
def ajax_to_internal_cls(cls)
link_to("#{cls.id}".html_safe,
- ontology_path(cls.explore.ontology.acronym, p: 'classes', conceptid: cls.id), target: "_blank")
+ ontology_path(cls.explore.ontology.acronym, p: 'classes', conceptid: cls.id), target: '_blank')
end
# to get the apikey from the interportal instance of the interportal class.
@@ -60,7 +102,7 @@ def ajax_to_internal_cls(cls)
def get_inter_portal_acronym(class_ui_url)
if !INTERPORTAL_HASH.nil?
INTERPORTAL_HASH.each do |key, value|
- if class_ui_url.start_with?(value["ui"])
+ if class_ui_url.start_with?(value['ui'])
return key
else
return nil
@@ -71,11 +113,11 @@ def get_inter_portal_acronym(class_ui_url)
# method to extract the prefLabel from the external class URI
def get_label_for_external_cls(class_uri)
- if class_uri.include? "#"
- prefLabel = class_uri.split("#")[-1]
- else
- prefLabel = class_uri.split("/")[-1]
- end
+ prefLabel = if class_uri.include? '#'
+ class_uri.split('#')[-1]
+ else
+ class_uri.split('/')[-1]
+ end
return prefLabel
end
@@ -86,11 +128,11 @@ def ajax_to_external_cls(cls)
# Replace the inter_portal mapping ontology URI (that link to the API) by the link to the ontology in the UI
def get_inter_portal_ui_link(uri, process_name)
process_name = '' if process_name.nil?
- interportal_acronym = process_name.split(" ")[2]
- if interportal_acronym.nil? || interportal_acronym.empty?
+ interportal_acronym = process_name.split(' ')[2]
+ if interportal_acronym.nil? || interportal_acronym.empty? || INTERPORTAL_HASH[interportal_acronym].nil?
uri
else
- uri.sub!(INTERPORTAL_HASH[interportal_acronym]["api"], INTERPORTAL_HASH[interportal_acronym]["ui"])
+ uri.sub!(INTERPORTAL_HASH[interportal_acronym]['api'], INTERPORTAL_HASH[interportal_acronym]['ui'])
end
end
@@ -99,66 +141,14 @@ def internal_mapping?(cls)
end
def inter_portal_mapping?(cls)
- !internal_mapping?(cls) && cls.links.has_key?("ui")
- end
-
- def get_mappings_target_params
- mapping_type = Array(params[:mapping_type]).first
- external = true
- case mapping_type
- when 'interportal'
- ontology_to = "#{params[:map_to_interportal]}/ontologies/#{params[:map_to_interportal_ontology]}"
- concept_to_id = params[:map_to_interportal_class]
- when 'external'
- ontology_to = params[:map_to_external_ontology]
- concept_to_id = params[:map_to_external_class]
- else
- ontology_to = params[:map_to_bioportal_ontology_id]
- concept_to_id = params[:map_to_bioportal_full_id]
- external = false
- end
- [ontology_to, concept_to_id, external]
- end
-
- def set_mapping_target(concept_to_id:, ontology_to:, mapping_type: )
- case mapping_type
- when 'interportal'
- @map_to_interportal, @map_to_interportal_ontology = ontology_to.match(%r{(.*)/ontologies/(.*)}).to_a[1..]
- @map_to_interportal_class = concept_to_id
- when 'external'
- @map_to_external_ontology = ontology_to
- @map_to_external_class = concept_to_id
- else
- @map_to_bioportal_ontology_id = ontology_to
- @map_to_bioportal_full_id = concept_to_id
- end
- end
-
- def get_mappings_target
- ontology_to, concept_to_id, external_mapping = get_mappings_target_params
- target = ''
- if external_mapping
- target_ontology = ontology_to
- target = concept_to_id
- else
- if helpers.uri?(ontology_to)
- target_ontology = LinkedData::Client::Models::Ontology.find(ontology_to)
- else
- target_ontology = LinkedData::Client::Models::Ontology.find_by_acronym(ontology_to).first
- end
- if target_ontology
- target = target_ontology.explore.single_class(concept_to_id).id
- target_ontology = target_ontology.id
- end
- end
- [target_ontology, target, external_mapping]
+ !internal_mapping?(cls) && cls.links.has_key?('ui')
end
def type?(type)
@mapping_type.nil? && type.eql?('internal') || @mapping_type.eql?(type)
end
- def concept_mappings_loader(ontology_acronym: ,concept_id: )
+ def concept_mappings_loader(ontology_acronym:, concept_id:)
content_tag(:span, id: 'mapping_count') do
concat(content_tag(:div, class: 'concepts-mapping-count ml-1 mr-1') do
render(TurboFrameComponent.new(
@@ -173,21 +163,23 @@ def concept_mappings_loader(ontology_acronym: ,concept_id: )
end
def client_filled_modal
- link_to_modal "", ""
+ link_to_modal '', ''
end
def mappings_bubble_view_legend
content_tag(:div, class: 'mappings-bubble-view-legend') do
- mappings_legend_section(t('mappings.bubble_view_legend.bubble_size'), t('mappings.bubble_view_legend.bubble_size_desc'), 'mappings-bubble-size-legend') +
+ mappings_legend_section(t('mappings.bubble_view_legend.bubble_size'),
+ t('mappings.bubble_view_legend.bubble_size_desc'), 'mappings-bubble-size-legend') +
mappings_legend_section(
- t('mappings.bubble_view_legend.color_degree'),t('mappings.bubble_view_legend.color_degree_desc'),'mappings-bubble-color-legend') +
+ t('mappings.bubble_view_legend.color_degree'), t('mappings.bubble_view_legend.color_degree_desc'), 'mappings-bubble-color-legend') +
content_tag(:div, class: 'content-container') do
content_tag(:div, class: 'bubble-view-legend-item') do
content_tag(:div, class: 'title') do
- content_tag(:div, t('mappings.bubble_view_legend.yellow_bubble'), class: 'd-inline') + content_tag(:span, t('mappings.bubble_view_legend.selected_bubble'))
+ content_tag(:div, t('mappings.bubble_view_legend.yellow_bubble'),
+ class: 'd-inline') + content_tag(:span, t('mappings.bubble_view_legend.selected_bubble'))
end +
- content_tag(:div, class: "mappings-bubble-size-legend d-flex justify-content-center") do
- content_tag(:div, '', class: "bubble yellow")
+ content_tag(:div, class: 'mappings-bubble-size-legend d-flex justify-content-center') do
+ content_tag(:div, '', class: 'bubble yellow')
end
end
end
@@ -209,7 +201,7 @@ def mappings_legend_section(title_text, description_text, css_class)
def mappings_legend(css_class)
content_tag(:div, class: css_class) do
content_tag(:div, t('mappings.bubble_view_legend.less_mappings'), class: 'mappings-legend-text') +
- (1..6).map { |i| content_tag(:div, "", class: "bubble bubble#{i}") }.join.html_safe +
+ (1..6).map { |i| content_tag(:div, '', class: "bubble bubble#{i}") }.join.html_safe +
content_tag(:div, t('mappings.bubble_view_legend.more_mappings'), class: 'mappings-legend-text')
end
end
diff --git a/app/helpers/metrics_helper.rb b/app/helpers/metrics_helper.rb
new file mode 100644
index 0000000000..5eb39444ce
--- /dev/null
+++ b/app/helpers/metrics_helper.rb
@@ -0,0 +1,69 @@
+module MetricsHelper
+
+ def portal_metrics(analytics)
+ ontologies_acronym = if analytics.empty?
+ LinkedData::Client::Models::Ontology.all.map { |x| x.acronym }
+ else
+ analytics.keys
+ end
+
+ metrics = ontologies_metrics(ontologies_acronym)
+
+ ont_count = ontologies_acronym.size
+ cls_count = metrics[:classes]
+ individuals_count = metrics[:individuals]
+ prop_count = metrics[:properties]
+ map_count = total_mapping_count(ontologies_acronym)
+ projects_count = projects_count(ontologies_acronym)
+ users_count = LinkedData::Client::Models::User.all.length
+
+ {
+ ontologies_count: ont_count,
+ class_count: cls_count,
+ individuals_count: individuals_count,
+ properties_count: prop_count,
+ mappings_count: map_count,
+ projects_count: projects_count,
+ users_count: users_count
+ }
+ end
+
+ def ontologies_metrics(ontologies_acronym = [])
+ metrics = LinkedData::Client::Models::Metrics.all
+
+ metrics.each_with_object(Hash.new(0)) do |h, sum|
+ acronym = h.submission&.first&.split('/')&.dig(-3)
+ next nil if acronym.nil?
+ next nil unless ontologies_acronym.blank? || ontologies_acronym.include?(acronym)
+
+ h.to_hash.slice(:classes, :properties, :individuals).each { |k, v| sum[k] += v }
+ end
+ end
+
+ private
+
+ def projects_count(ontologies_acronym = [])
+ projects = LinkedData::Client::Models::Project.all
+ projects.select! { |p| ontologies_acronym.intersection(p.ontologyUsed.map{|x| x.split('/').last}).any? } unless ontologies_acronym.empty?
+ projects.size
+ end
+
+ def total_mapping_count(ontologies_acronym = [])
+ total_count = 0
+ begin
+ stats = LinkedData::Client::HTTP.get(MappingStatistics::MAPPING_STATISTICS_URL)
+ unless stats.blank?
+ stats = stats.to_h.compact
+ # Some of the mapping counts are erroneously stored as strings
+ stats.select!{ |acronym, count| ontologies_acronym.include?(acronym.to_s) } if helpers.at_slice?
+ stats.transform_values!(&:to_i)
+ total_count = stats.values.sum
+ end
+ rescue StandardError => e
+ LOG.add :error, e.message
+ end
+
+ total_count
+ end
+
+end
diff --git a/app/helpers/multi_languages_helper.rb b/app/helpers/multi_languages_helper.rb
index 9e21f2e0ed..6e127b94e0 100644
--- a/app/helpers/multi_languages_helper.rb
+++ b/app/helpers/multi_languages_helper.rb
@@ -153,7 +153,7 @@ def select_language_label(concept_label, platform_languages = %i[en fr])
end
end
- concept_value || concept.to_a.first
+ concept_value || concept.reject { |k| k.to_s.eql?('@none') }.first || concept.first
end
def main_language_label(label)
diff --git a/app/helpers/ontologies_helper.rb b/app/helpers/ontologies_helper.rb
index 33adc9efd5..f3a7ae05f7 100644
--- a/app/helpers/ontologies_helper.rb
+++ b/app/helpers/ontologies_helper.rb
@@ -71,32 +71,10 @@ def ontology_alternative_names(submission = @submission_latest)
end)
end
end
+
def private_ontology_icon(is_private)
raw(content_tag(:i, '', class: 'fas fa-key', title: t('ontologies.private_ontology'))) if is_private
end
- def browse_filter_section_label(key)
- labels = {
- categories: t('ontologies.categories'),
- groups: t('ontologies.groups'),
- hasFormalityLevel: t('ontologies.formality_levels'),
- isOfType: t('ontologies.ontology_types'),
- naturalLanguage: t('ontologies.natural_languages')
- }
-
- labels[key] || key.to_s.underscore.humanize.capitalize
- end
-
- def browser_counter_loader
- content_tag(:div, class: "browse-desc-text", style: "margin-bottom: 15px;") do
- content_tag(:div, class: "d-flex align-items-center") do
- str = content_tag(:span, t('ontologies.showing'))
- str += content_tag(:span, "", class: "p-1 p-2", style: "color: #a7a7a7;") do
- render LoaderComponent.new(small: true)
- end
- str
- end
- end
- end
def ontologies_browse_skeleton(pagesize = 5)
pagesize.times do
@@ -530,6 +508,51 @@ def language_selector_hidden_tag(section)
data: { controller: "language-change", 'language-change-section-value': section, action: "change->language-change#dispatchLangChangeEvent" }
end
+ def ontology_object_json_link(ontology_acronym, object_type, id)
+ "#{rest_url}/ontologies/#{ontology_acronym}/#{object_type}/#{escape(id)}?display=all&apikey=#{get_apikey}"
+ end
+
+ def render_permalink_link
+ content_tag(:div, class: 'mx-1') do
+ link_to("#classPermalinkModal", class: "class-permalink nav-link", title: t('concepts.permanent_link_class'), aria: { label: t('concepts.permanent_link_class') }, data: { toggle: "modal", current_purl: @current_purl }) do
+ content_tag(:i, '', class: "fas fa-link", aria: { hidden: "true" })
+ end
+ end
+ end
+
+ def render_concepts_json_button(link)
+ content_tag(:div, class: 'concepts_json_button') do
+ render RoundedButtonComponent.new(link: link, target: '_blank')
+ end
+ end
+
+
+ def ontology_object_details_component(frame_id: , ontology_id:, objects_title:, object:, &block)
+ render TurboFrameComponent.new(id: frame_id, data: {"turbo-frame-target": "frame"}) do
+ return if !object.present?
+ return alert_component(object.errors.join) if object.errors
+
+ ontology_object_tabs_component(ontology_id: ontology_id, objects_title: objects_title, object_id: object["@id"]) do |tabs|
+ tab_item_component(container_tabs: tabs, title: t('concepts.details'), path: '#details', selected: true) do
+ capture(&block)
+ end
+ end
+ end
+ end
+
+ def ontology_object_tabs_component(ontology_id:, objects_title:, object_id:, &block)
+ resource_url = ontology_object_json_link(ontology_id, objects_title, object_id)
+ render TabsContainerComponent.new(type: 'outline') do |c|
+ concat(c.pinned_right do
+ content_tag(:div, '', 'data-concepts-json-target': 'button') do
+ concat(render_permalink_link) if $PURL_ENABLED
+ concat(render_concepts_json_button(resource_url))
+ end
+ end)
+
+ capture(c, &block)
+ end
+ end
def display_complex_text(definitions)
@@ -770,7 +793,6 @@ def n_triples_to_table(n_triples_string)
end
end
- private
def submission_languages(submission = @submission)
Array(submission&.naturalLanguage).map { |natural_language| natural_language["iso639"] && natural_language.split('/').last }.compact
@@ -780,12 +802,87 @@ def id_to_acronym(id)
id.split('/').last
end
- def browse_taxonomy_tooltip(texonomy)
- content_tag(:div, class: 'd-flex') do
- content_tag(:div, "See more information about #{texonomy} in ", class: 'mr-1') +
- content_tag(:a, 'here', href: "/#{texonomy}", target: '_blank')
+ def browse_taxonomy_tooltip(taxonomy_type)
+ return nil unless taxonomy_type.eql?("categories") || taxonomy_type.eql?("groups")
+
+ content_tag(:div, class: '') do
+ content_tag(:span, "See more information about #{taxonomy_type} in ", class: 'mr-1') +
+ content_tag(:a, 'here', href: "/#{taxonomy_type}", target: '_blank')
+ end
+ end
+
+ def browse_chip_filter(key:, object:, values:, countable: true, count: nil)
+ title = (key.to_s.eql?("categories") || key.to_s.eql?("groups")) ? nil : ''
+ checked = values.any? { |obj| [link_last_part(object["id"]), link_last_part(object["value"])].include?(obj) }
+
+ group_chip_component(name: key, object: object, checked: checked, title: title) do |c|
+ c.count { browse_chip_count_badge(key: key, id: object["id"], count: count) } if countable
+ end
+ end
+
+ def browse_chip_count_badge(id:, key:, count: nil)
+ content_tag :span, class: 'badge badge-light ml-1' do
+ turbo_frame_tag("count_#{key}_#{link_last_part(id)}", busy: true) +
+ if count || count == 0
+ content_tag(:span, count.to_s, class: "hide-if-loading #{count.zero? ? 'disabled' : ''}")
+ else
+ content_tag(:span, class: 'show-if-loading') do
+ loader_component(small: true, type: nil)
+ end
+ end
+ end
+ end
+
+ def browse_filter_section_label(key)
+ labels = {
+ categories: t('ontologies.categories'),
+ groups: t('ontologies.groups'),
+ hasFormalityLevel: t('ontologies.formality_levels'),
+ isOfType: t('ontologies.ontology_types'),
+ naturalLanguage: t('ontologies.natural_languages')
+ }
+
+ labels[key] || key.to_s.underscore.humanize.capitalize
+ end
+
+ def browse_filter_section_header(key: nil, count: nil, title: nil)
+ render Display::HeaderComponent.new(tooltip: key ? browse_taxonomy_tooltip(key.to_s) : nil) do
+ content_tag(:span, class: "browse-filter-title-bar") do
+ concat title || browse_filter_section_label(key)
+
+ concat content_tag(:span, count, class: "badge badge-primary mx-1",
+ "data-show-filter-count-target": "countSpan",
+ style: "#{count&.positive? ? '' : 'display: none;'}")
+ end
+
+ end
+ end
+
+ def browse_filter_section_body(checked_values: , key:, objects:, countable: true, counts: nil)
+ output = content_tag(:div, class: "browse-filter-checks-container px-3") do
+ Array(objects).map do |object|
+ count = counts ? counts[link_last_part(object["id"])] || 0 : nil
+ concat browse_chip_filter(key: key, object: object, values: checked_values, countable: countable, count: count)
+ end
+ end
+
+ if key.to_s.include?("categories")
+ turbo_frame_tag('categories_refresh_for_federation') { output.html_safe }
+ else
+ output
end
end
+ def browser_counter_loader
+ content_tag(:div, class: "browse-desc-text", style: "margin-bottom: 15px;") do
+ content_tag(:div, class: "d-flex align-items-center") do
+ str = content_tag(:span, t('ontologies.showing'))
+ str += content_tag(:span, "", class: "p-1 p-2", style: "color: #a7a7a7;") do
+ render LoaderComponent.new(small: true)
+ end
+ str
+ end
+ end
+ end
end
diff --git a/app/helpers/urls_helper.rb b/app/helpers/urls_helper.rb
index 5e26db110b..2c149e29d4 100644
--- a/app/helpers/urls_helper.rb
+++ b/app/helpers/urls_helper.rb
@@ -1,4 +1,40 @@
module UrlsHelper
+ def url_to_endpoint(url)
+ uri = URI.parse(url)
+ endpoint = uri.path.sub(/^\//, '')
+ endpoint
+ end
+ def rest_hostname
+ extract_hostname($REST_URL)
+ end
+
+ def extract_hostname(url)
+ begin
+ uri = URI.parse(url)
+ uri.hostname
+ rescue URI::InvalidURIError
+ url
+ end
+ end
+
+ def link?(str)
+ # Regular expression to match strings starting with "http://" or "https://"
+ link_pattern = /\Ahttps?:\/\//
+ str = str&.strip
+ # Check if the string matches the pattern
+ !!(str =~ link_pattern)
+ end
+
+ def link_last_part(url)
+ return "" if url.nil?
+
+ if url.include?('#')
+ url.split('#').last
+ else
+ url.split('/').last
+ end
+ end
+
def escape(string)
CGI.escape(string) if string
end
@@ -6,4 +42,8 @@ def escape(string)
def unescape(string)
CGI.unescape(string) if string
end
+
+ def encode_param(string)
+ escape(string)
+ end
end
diff --git a/app/javascript/component_controllers/index.js b/app/javascript/component_controllers/index.js
index c0d3bcd20f..bd6df841a2 100644
--- a/app/javascript/component_controllers/index.js
+++ b/app/javascript/component_controllers/index.js
@@ -25,6 +25,7 @@ import Table_component_controller from '../../components/table_component/table_c
import clipboard_component_controller from '../../components/clipboard_component/clipboard_component_controller'
import range_slider_component_controller from '../../components/input/range_slider_component/range_slider_component_controller'
import RDFHighlighter from '../../components/display/rdf_highlighter_component/rdf_highlighter_component_controller'
+import FederationController from "../../components/federated_portal_button_component/federated_portal_button_component_controller"
application.register("rdf-highlighter", RDFHighlighter)
application.register('turbo-modal', TurboModalController)
@@ -35,10 +36,12 @@ application.register('subscribe-notes', Ontology_subscribe_button_component_cont
application.register('search-input', Search_input_component_controller)
application.register('tabs-container', Tabs_container_component_controller)
application.register('circle-progress-bar', CircleProgressBarComponentController)
-application.register('alert-component', alert_component_controller)
+application.register('alert-component', alert_component_controller)
application.register('progress-pages', Progress_pages_component_controller)
application.register('reveal-component', Reveal_component_controller)
application.register('table-component', Table_component_controller)
application.register('clipboard', clipboard_component_controller)
-application.register('range-slider', range_slider_component_controller)
\ No newline at end of file
+
+application.register('range-slider', range_slider_component_controller)
+application.register("federation-portals-colors", FederationController)
diff --git a/app/javascript/controllers/browse_filters_controller.js b/app/javascript/controllers/browse_filters_controller.js
index 74272dc26a..51eff387bb 100644
--- a/app/javascript/controllers/browse_filters_controller.js
+++ b/app/javascript/controllers/browse_filters_controller.js
@@ -2,6 +2,7 @@ import {Controller} from "@hotwired/stimulus"
import debounce from "debounce"
// Connects to data-controller="browse-filters"
export default class extends Controller {
+ static targets = ['sort']
initialize() {
this.dispatchInputEvent = debounce(this.dispatchInputEvent.bind(this), 700);
@@ -46,11 +47,16 @@ export default class extends Controller {
filter = "private_only"
break;
default:
- checks = this.#getSelectedChecks().map(x => x.value)
+ checks = this.#getSelectedChecks(event).map(x => x.value)
filter = event.target.name
}
-
this.#dispatchEvent(filter, checks)
+ event.stopPropagation()
+ }
+
+ federationChange(event){
+ this.sortTarget.value = "ontology_name"
+ this.sortTarget.dispatchEvent(new Event('change', { bubbles: true }))
}
@@ -63,11 +69,10 @@ export default class extends Controller {
data: data
}, bubbles: true
});
-
this.element.dispatchEvent(customEvent);
}
- #getSelectedChecks() {
- return Array.from(this.element.querySelectorAll('input:checked'))
+ #getSelectedChecks(event) {
+ return Array.from(event.currentTarget.querySelectorAll('input:checked'))
}
}
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
index 24f39bf453..fa09b85416 100644
--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
@@ -105,4 +105,4 @@ 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)
diff --git a/app/javascript/controllers/simple_tree_controller.js b/app/javascript/controllers/simple_tree_controller.js
index 858bfde2c8..4cece829a7 100644
--- a/app/javascript/controllers/simple_tree_controller.js
+++ b/app/javascript/controllers/simple_tree_controller.js
@@ -1,4 +1,7 @@
import { Controller } from '@hotwired/stimulus'
+
+const TREE_VIEW_PAGES = ['classes', 'properties', 'schemes', 'collections', 'instances']
+
// Connects to data-controller="simple-tree"
export default class extends Controller {
@@ -7,17 +10,7 @@ export default class extends Controller {
}
connect () {
- setTimeout(() => {
- let activeElem = this.element.querySelector('.tree-link.active');
- if (activeElem) {
- activeElem.scrollIntoView({ block: 'center' });
- window.scrollTo({top: 0,});
- if (this.autoClickValue) {
- activeElem.click();
- }
- }
- this.#onClickTooManyChildrenInit();
- }, 0);
+ this.#centerTreeView()
}
select (event) {
@@ -33,6 +26,29 @@ export default class extends Controller {
event.target.nextElementSibling.nextElementSibling.classList.toggle('hidden')
}
+ #centerTreeView() {
+ setTimeout(() => {
+ const location = window.location.href;
+
+ const isTreeViewPage = TREE_VIEW_PAGES.some(param => location.includes(`p=${param}`));
+
+ if (isTreeViewPage) {
+ const activeElem = this.element.querySelector('.tree-link.active');
+
+ if (activeElem) {
+ activeElem.scrollIntoView({ block: 'center' });
+ window.scrollTo({ top: 0 });
+
+ if (this.autoClickValue) {
+ activeElem.click();
+ }
+ }
+
+ this.#onClickTooManyChildrenInit();
+ }
+ }, 0);
+ }
+
#onClickTooManyChildrenInit () {
jQuery('.too_many_children_override').live('click', (event) => {
event.preventDefault()
diff --git a/app/javascript/controllers/turbo_frame_controller.js b/app/javascript/controllers/turbo_frame_controller.js
index 6520acb64a..aacc99eca1 100644
--- a/app/javascript/controllers/turbo_frame_controller.js
+++ b/app/javascript/controllers/turbo_frame_controller.js
@@ -32,7 +32,6 @@ export default class extends Controller {
this.urlValue = this.#updatedPageUrl(data)
this.frame.src = this.urlValue
-
}
}
@@ -44,7 +43,7 @@ export default class extends Controller {
if (currentDisplayedUrl.toString().includes(this.urlValue)){
return true
- } else if (currentDisplayedUrl.searchParams.get('p') === initUrl.searchParams.get('p')){
+ } else if (currentDisplayedUrl.searchParams.has('p') && currentDisplayedUrl.searchParams.get('p') === initUrl.searchParams.get('p')){
// this is a custom fix for only the ontology viewer page,
// that use the parameter ?p=section to tell which section is displayed
return true
diff --git a/app/views/admin/categories/_form.html.haml b/app/views/admin/categories/_form.html.haml
index 75974b5a3a..fff9d1a6cf 100644
--- a/app/views/admin/categories/_form.html.haml
+++ b/app/views/admin/categories/_form.html.haml
@@ -34,7 +34,7 @@
%th
= t('admin.categories.form.parent_category')
%td.top
- = categories_select(id: 'category_parent_select', name: 'category[parentCategory]', selected: @category&.parentCategory)
+ = categories_select(id: 'category_parent_select', name: 'category[parentCategory][]', selected: @category&.parentCategory)
- unless new_record
%tr
%th
diff --git a/app/views/annotator/index.html.haml b/app/views/annotator/index.html.haml
index 8b83bab08c..8fcdaf4bcd 100644
--- a/app/views/annotator/index.html.haml
+++ b/app/views/annotator/index.html.haml
@@ -27,7 +27,7 @@
= render(ChipsComponent.new(name: 'exclude_synonyms', label: t('annotator.exclude_synonyms'), checked: params[:exclude_synonyms]))
.select-ontologies
- = ontologies_selector(id:'annotator_page_ontologies', label: 'Select ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(','))
+ = ontologies_selector(id:'annotator_page_ontologies', label: t('annotator.select_ontologies') ,name: 'ontologies[]', selected: params[:ontologies]&.split(','))
= show_advanced_options_button(text: t('show_advanced_options'), init: @advanced_options_open)
= hide_advanced_options_button(text: t('hide_advanced_options'), init: @advanced_options_open)
.more-advanced-options{'data-reveal-component-target': 'item', class: "#{@advanced_options_open ? '' : 'd-none'}"}
diff --git a/app/views/collections/_collection.html.haml b/app/views/collections/_collection.html.haml
deleted file mode 100644
index 16503de745..0000000000
--- a/app/views/collections/_collection.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-= turbo_frame_tag 'collection' do
- = render ConceptDetailsComponent.new(id:'collection-label', acronym: @ontology.acronym, concept_id: collection.id,
- properties: collection.properties,
- top_keys: %w[created modified comment note],
- bottom_keys: [],
- exclude_keys: %w[member]) do |c|
- - c.header(stripped: true) do |t|
- - t.add_row({th: t("collections.id")}, {td: link_to_with_actions(collection["@id"], acronym: @ontology.acronym)})
- - t.add_row({th: t("collections.preferred_name")}, {td: display_in_multiple_languages(get_collection_label(collection))})
- - t.add_row({th: t("collections.members_count")}) do |r|
- - r.td do
- = link_to collection["memberCount"], "/ontologies/" + @ontology.acronym + "/?p=classes&sub_menu=list&concept_collections=" + collection["@id"], 'data-turbo-frame':'_top'
- - t.add_row({th: t("collections.type")}, {td: collection["@type"]})
diff --git a/app/views/collections/_show.html.haml b/app/views/collections/_show.html.haml
new file mode 100644
index 0000000000..d79a3e47c8
--- /dev/null
+++ b/app/views/collections/_show.html.haml
@@ -0,0 +1,14 @@
+= ontology_object_details_component(frame_id: "collection", ontology_id: @ontology.acronym, objects_title: "collections", object: @collection) do
+ = render ConceptDetailsComponent.new(id:'collection-label', acronym: @ontology.acronym, concept_id: @collection.id,
+ properties: @collection.properties,
+ top_keys: %w[created modified comment note],
+ bottom_keys: [],
+ exclude_keys: %w[member]) do |c|
+ - c.header(stripped: true) do |t|
+ - t.add_row({th: t("collections.id")}, {td: link_to_with_actions(@collection["@id"], acronym: @ontology.acronym)})
+ - t.add_row({th: t("collections.preferred_name")}, {td: display_in_multiple_languages(get_collection_label(@collection))})
+ - t.add_row({th: t("collections.members_count")}) do |r|
+ - r.td do
+ = link_to @collection["memberCount"], "/ontologies/" + @ontology.acronym + "/?p=classes&sub_menu=list&concept_collections=" + @collection["@id"], 'data-turbo-frame':'_top'
+ - t.add_row({th: t("collections.type")}, {td: @collection["@type"]})
+
diff --git a/app/views/collections/show.html.haml b/app/views/collections/show.html.haml
deleted file mode 100644
index 4ef79429cf..0000000000
--- a/app/views/collections/show.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= render partial: 'collection', locals: {collection: @collection}
\ No newline at end of file
diff --git a/app/views/concepts/_show.html.haml b/app/views/concepts/_show.html.haml
index a30f061fdf..8d690b65fd 100644
--- a/app/views/concepts/_show.html.haml
+++ b/app/views/concepts/_show.html.haml
@@ -4,63 +4,32 @@
= t('concepts.use_jump_to')
- else
%div{'data-controller': 'concepts-json', 'data-action': 'click->concepts-json#update'}
- = render TabsContainerComponent.new(type:'outline') do |c|
- - c.pinned_right do
- - if $PURL_ENABLED
- %div.mx-1
- = link_to("#classPermalinkModal", class: "class-permalink nav-link", title: t('concepts.permanent_link_class'), aria: {label: t('concepts.permanent_link_class')}, data: {toggle: "modal", current_purl: "#{@current_purl}"}) do
- %i{class: "fas fa-link", aria: {hidden: "true"}}
- %div{'data-concepts-json-target': 'button'}
- .concepts_json_button
- = render RoundedButtonComponent.new(link: "#{@ontology.id}/classes/#{escape(@concept.id)}?display=all&apikey=#{get_apikey}", target:'_blank')
+ = ontology_object_tabs_component(ontology_id: @ontology.acronym, objects_title: "classes", object_id: @concept.id) do |c|
- apikey = "apikey=#{get_apikey}"
- baseClassUrl = "#{@ontology.id}/classes/#{escape(@concept.id)}"
- - c.item(title: t('concepts.details'), path: '#details', selected: true, json_link: "#{baseClassUrl}?#{apikey}&display=all")
-
- - unless skos?
- - c.item(id: 'instances', path: '#instances', json_link: "#{baseClassUrl}/instances?#{apikey}") do
- = t('concepts.instances')
- (
- %span#concept_instances_sorted_list_count
- )
-
- - c.item(title: t('concepts.visualization'), path: '#visualization')
-
- - c.item(id: 'notes', path: '#notes', json_link: "#{baseClassUrl}/notes?#{apikey}") do
- = t('concepts.notes')
- %span#note_count_wrapper
- (
- %span#note_count= @notes.length
- )
- - c.item(id: 'mappings', path: '#mappings', json_link: "#{baseClassUrl}/mappings?#{apikey}") do
- .d-flex
- #{t('concepts.mappings')}
- (
- = concept_mappings_loader(ontology_acronym: @ontology.acronym, concept_id: @concept.id)
- )
-
- - if @enable_ontolobridge
- - c.item(title: t('concepts.new_term_requests'), path: '#request_term')
-
- - c.item_content do
+ - tab_item_component(container_tabs: c, title: t('concepts.details'), path: '#details', selected: true, json_link: "#{baseClassUrl}?#{apikey}&display=all") do
= render :partial =>'/concepts/details'
- unless skos?
- - c.item_content do
+ - count_span = content_tag(:span, "#{t('concepts.instances')} (#{content_tag(:span, "", id: 'concept_instances_sorted_list_count')})".html_safe)
+ - tab_item_component(container_tabs: c, title: count_span, path: '#instances', json_link: "#{baseClassUrl}/instances?#{apikey}") do
= render :partial =>'instances/instances' , locals: {id: "class-instances-data-table"}
- - c.item_content do
+
+
+ - tab_item_component(container_tabs: c, title: t('concepts.visualization'), path: '#visualization') do
= render :partial =>'/concepts/biomixer'
- - c.item_content do
+
+ - count_span = content_tag(:span, "#{t('concepts.notes')} (#{content_tag(:span, @notes.length, id: 'note_count')})".html_safe)
+ - tab_item_component(container_tabs: c, title: count_span, path: '#notes', json_link: "#{baseClassUrl}/notes?#{apikey}") do
= render :partial =>'/notes/list'
- - c.item_content do
+
+ - count_span = content_tag(:span, "#{t('concepts.mappings')} (#{content_tag(:span, concept_mappings_loader(ontology_acronym: @ontology.acronym, concept_id: @concept.id))})".html_safe, class: "d-flex")
+ - tab_item_component(container_tabs: c, title: count_span, path: '#mappings', json_link: "#{baseClassUrl}/mappings?#{apikey}") do
= render TurboFrameComponent.new(id:'concept_mappings',
src:"/ajax/mappings/get_concept_table?ontologyid=#{@ontology.acronym}&conceptid=#{CGI.escape(@concept.id)}")
- - if @enable_ontolobridge
- - c.item_content do
- = render :partial =>'/concepts/request_term'
:javascript
jQuery(document).ready(function(){
diff --git a/app/views/home/cookies.html.haml b/app/views/home/cookies.html.haml
index d02f687a9b..513df903f7 100644
--- a/app/views/home/cookies.html.haml
+++ b/app/views/home/cookies.html.haml
@@ -10,4 +10,4 @@
%div
= link_button_component(id:'accept-cookie-selector', value: t('cookies_modal.accept_button'), href: cookies_path(cookies: true), size: "slim", variant: 'primary')
%div.cookie-privacy-link
- = link_to t('cookies_modal.privacy_link'), $FOOTER_LINKS.dig(:sections, :agreements, :privacy_policy)
+ = link_to t('cookies_modal.privacy_link'), "#{$FOOTER_LINKS.dig(:sections, :agreements, :privacy_policy)}#{t('cookies_modal.privacy_policy_anchor')}"
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 83fc781581..8bc39eba97 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -22,17 +22,17 @@
.home-bubble.home-bubble-one
%a.h5{href:"/ontologies/#{@anal_ont_names[0]}", style: "color: white !important"}
= @anal_ont_names[0]
- %p
+ %p
= @anal_ont_numbers[0].to_s + " " + t('visits.visits')
.home-bubble.home-bubble-two
%a.h5{href:"/ontologies/#{@anal_ont_names[1]}", style: "color: white !important"}
= @anal_ont_names[1]
- %p
+ %p
= @anal_ont_numbers[1].to_s + " " + t('visits.visits')
.home-bubble.home-bubble-three
%a.h5{href:"/ontologies/#{@anal_ont_names[2]}", style: "color: white !important"}
= @anal_ont_names[2]
- %p
+ %p
= @anal_ont_numbers[2].to_s + " " + t('visits.visits')
%a.home-bubble.home-bubble-four{:href => "/visits"}
.h5 ...
@@ -44,7 +44,7 @@
%p
= t('home.index.tagline')
= ontologies_content_autocomplete
-
+
.home-body-container
.home-section
%h4= t('home.ontology_upload')
@@ -115,19 +115,19 @@
%a{:href => "/landscape#fairness_assessment"}
%div.home-fair-details
%p= t('home.fair_details')
-
+
.home-sub-section-right
%h4= t('home.twitter_news')
%hr.home-section-line
.home-card.home-twitter-news
%a.twitter-timeline{"data-height" => "360", :href => "https://twitter.com/lagroportal?ref_src=twsrc%5Etfw"}
- .home-twitter-loader
+ .home-twitter-loader
= render LoaderComponent.new(type: 'pulsing')
%script{:async => "", :charset => "utf-8", :src => "https://platform.twitter.com/widgets.js"}
.home-section
%h4
- = t('home.agroportal_figures', site: portal_name)
+ = t('home.agroportal_figures', site: current_slice_name || portal_name)
%hr.home-section-line/
.home-statistics-container
.home-statistics
@@ -135,54 +135,54 @@
%hr/
%div
%h4
- = format_number_abbreviated(@ont_count)
+ = format_number_abbreviated(@metrics[:ontologies_count])
%p= t("home.ontologies")
.home-statistics-item
%hr/
%div
%h4
- = format_number_abbreviated(@cls_count)
+ = format_number_abbreviated(@metrics[:class_count])
%p= t("home.classes")
.home-statistics-item
%hr/
%div
%h4
- = format_number_abbreviated(@individuals_count)
+ = format_number_abbreviated(@metrics[:individuals_count])
%p= t("home.individuals")
.home-statistics-item
%hr/
%div
%h4
- = format_number_abbreviated(@prop_count)
+ = format_number_abbreviated(@metrics[:properties_count])
%p= t("home.properties")
.home-statistics-item
%hr/
%div
%h4
- = format_number_abbreviated(@projects_count)
+ = format_number_abbreviated(@metrics[:projects_count])
%p= t("home.projects")
.home-statistics-item
%hr/
%div
%h4
- = format_number_abbreviated(@map_count)
+ = format_number_abbreviated(@metrics[:mappings_count])
%p= t("home.mappings")
.home-statistics-item
%hr/
%div
%h4
- = format_number_abbreviated(@users_count)
+ = format_number_abbreviated(@metrics[:users_count])
%p= t("home.users")
.home-statistics-link.justify-content-end{style: @analytics.empty? && "visibility: hidden"}
= link_to t("home.see_details"),'/statistics', target: "_blank"
- - if @slices
+ - if slices_enabled?
.home-section
.home-section-title
.text
= "#{portal_name} slices"
%hr.home-section-line/
- .home-section-description
+ .home-section-description
.div
= t('home.slices_description')
.home-slices-container
@@ -194,8 +194,8 @@
.home-slice-ontologies
= slice.ontologies.length
= inline_svg 'icons/slices.svg', width: "70", height: "70"
-
- .home-slice-name
+
+ .home-slice-name
= "#{slice.name} (#{slice.acronym})"
= render Buttons::RegularButtonComponent.new(id:'regular-button', value: t('home.suggest_slice'), variant: "secondary", state: "regular", href: '/feedback') do |btn|
- btn.icon_right do
@@ -208,17 +208,18 @@
= t('home.ontoportal_instances')
%hr.home-section-line/
- .home-section-description
+ .home-section-description
.div
= home_ontoportal_description
.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
- = inline_svg 'logo-white.svg', width: "35", height: "26"
- %p{style: "color: #{portal[:color]}"}
- = portal[:portal]
-
+ = portal_config_tooltip(portal[:name]) do
+ %div.text-center
+ = 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[:name]
+
.home-section
.home-section-title
.text
@@ -229,6 +230,8 @@
%a{href:logo[:url], target: "_blanc"}
%img{src: asset_path(logo[:img_src])}
+= init_federation_portals_status
+
:javascript
function submitAnnotator(){
document.getElementById("annotator_submit").click()
@@ -236,4 +239,3 @@
function submitRecommender(){
document.getElementById("recommender_submit").click()
}
-
diff --git a/app/views/home/portal_config.html.haml b/app/views/home/portal_config.html.haml
new file mode 100644
index 0000000000..b484e9431c
--- /dev/null
+++ b/app/views/home/portal_config.html.haml
@@ -0,0 +1,42 @@
+= turbo_frame_tag "portal_config_tooltip_#{params[:portal]}" do
+ .portal-configuration
+ .home-section-description.mb-1
+ .div.d-flex.align-items-center
+ %div
+ %div.text-center
+ = link_to @portal_config[:ui] || @config[:ui], target: '_blank', class: 'home-logo-instances mr-1 m-0', style: "background-color: #{@portal_config[:color] || @config[:color]}" do
+ = inline_svg 'logo-white.svg', width: "35", height: "26"
+ %div
+ %div.portal-configuration-title{style: "color: #{@portal_config[:color] || @config[:color]}"}
+ %h3
+ = @portal_config[:title] || @config[:name]
+ - if @portal_config[:numberOfArtefacts]
+ .portal-config-ontologies
+ = inline_svg_tag 'icons/ontology.svg'
+ %span
+ = "#{@portal_config[:numberOfArtefacts]} ontologies"
+ - if @portal_config[:description]
+ .portal-description
+ = @portal_config[:description]
+ - if @portal_config[:federated_portals]
+ %div.mb-1
+ .home-section-title
+ .text
+ Federated with
+ .d-flex.flex-wrap.my-1
+ - @portal_config[:federated_portals].to_h.values.compact.each do |portal|
+ .portal-config-federated-with
+ = link_to portal[:ui], target: '_blank', class: 'home-logo-instances-small', style: "background-color: #{portal[:color]};" do
+ = inline_svg 'logo-white.svg', width: "18", height: "13"
+ %p{style: "color: #{portal[:color]}"}
+ = portal[:name]
+
+ - if @portal_config[:fundedBy]
+ %div.mb-1
+ .home-section-title
+ .portal-config-title-text
+ = t('home.support_and_collaborations')
+ .home-support-items.d-flex.flex-wrap.my-1
+ - @portal_config[:fundedBy]&.each do |logo|
+ %a.mx-2.my-1{href:logo[:url], target: "_blanc"}
+ %img{src: asset_path(logo[:img_src]), width: "18", height: "13"}
diff --git a/app/views/home/tools.html.haml b/app/views/home/tools.html.haml
index 941d60ec74..d4f0455b69 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/instances/_details.html.haml b/app/views/instances/_details.html.haml
deleted file mode 100644
index 146dcd9464..0000000000
--- a/app/views/instances/_details.html.haml
+++ /dev/null
@@ -1,27 +0,0 @@
-= render TurboFrameComponent.new(id: params[:modal]&.to_s.eql?('true') ? modal_frame_id : 'instance_show') do
- - if @instance && @instance["@id"]
- %div
- - ontology_acronym = params[:ontology_id] || @ontology.acronym
- - filter_properties = ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "http://www.w3.org/2000/01/rdf-schema#label", "http://www.w3.org/2004/02/skos/core#prefLabel"]
-
- = render ConceptDetailsComponent.new(id:'instance-details', acronym: ontology_acronym, concept_id: @instance["@id"]) do |c|
- - c.header(stripped: true) do |t|
- - t.add_row({th: t("instances.id")}, {td: link_to_with_actions(@instance["@id"], acronym: @ontology.acronym) })
-
- - label = @instance['label'] || @instance['prefLabel']
- - unless label.blank?
- - t.add_row({th: t('instances.label') }, {td: label.join(',').html_safe})
-
- - types = @instance.types.reject{|x| x['NamedIndividual']}
- - unless types.empty?
- - t.add_row({th: t('instances.type') }) do |r|
- - r.td do
- = types.reject{|x| x['NamedIndividual']}.map {|cls| link_to_class(ontology_acronym,cls)}.join(', ').html_safe
- - properties = @instance[:properties].to_h.select{|k,v| !filter_properties.include? k.to_s}
- - properties.each do |prop|
- - if !prop[1].nil?
- - t.add_row({th: link_to_property(prop[0], ontology_acronym)}, {td: prop[1].map { |value| instance_property_value(value , ontology_acronym) }.join(', ').html_safe})
-
-
-
-
diff --git a/app/views/instances/_instances.html.haml b/app/views/instances/_instances.html.haml
index 78b8ce0c29..9e5ea8eb33 100644
--- a/app/views/instances/_instances.html.haml
+++ b/app/views/instances/_instances.html.haml
@@ -9,7 +9,7 @@
- if params[:p].eql?('instances')
%div#prop_contents{data: {'container-splitter-target': 'container'}}
- = render partial: 'instances/details'
+ = render partial: 'instances/show'
diff --git a/app/views/instances/_show.html.haml b/app/views/instances/_show.html.haml
new file mode 100644
index 0000000000..71fbc857ca
--- /dev/null
+++ b/app/views/instances/_show.html.haml
@@ -0,0 +1,22 @@
+= ontology_object_details_component(frame_id: params['modal'].eql?('true') ? modal_frame_id : "instance_show", ontology_id: @ontology.acronym, objects_title: "instances", object: @instance) do
+ = render ConceptDetailsComponent.new(id: 'instance-details', acronym: @ontology.acronym, concept_id: @instance["@id"]) do |c|
+ - c.header(stripped: true) do |t|
+ - t.add_row({ th: t("instances.id") }, { td: link_to_with_actions(@instance["@id"], acronym: @ontology.acronym) })
+
+ - label = @instance['label'] || @instance['prefLabel']
+ - t.add_row({ th: t('instances.label') }, { td: label.join(', ').html_safe }) unless label.blank?
+
+ - types = @instance.types.reject { |x| x['NamedIndividual'] }
+ - unless types.empty?
+ - t.add_row({ th: t('instances.type') }) do |r|
+ - r.td do
+ = types.map { |cls| link_to_class(@ontology.acronym, cls) }.join(', ').html_safe
+
+ - filter_properties = %w[http://www.w3.org/1999/02/22-rdf-syntax-ns#type http://www.w3.org/2000/01/rdf-schema#label http://www.w3.org/2004/02/skos/core#prefLabel]
+ - properties = @instance[:properties].to_h.reject { |k, _| filter_properties.include?(k.to_s) }
+ - properties.each do |prop, values|
+ - if values.present?
+ - t.add_row({ th: link_to_property(prop, @ontology.acronym) }, { td: values.map { |value| instance_property_value(value, @ontology.acronym) }.join(', ').html_safe })
+
+
+
diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml
index 9d89089615..68575a0c2b 100644
--- a/app/views/layouts/_footer.html.haml
+++ b/app/views/layouts/_footer.html.haml
@@ -20,8 +20,10 @@
%h2
= t("layout.footer."+key.to_s)
%div
+
- section_links.each do |section , link|
- %a{:href => link, :target => "_blank"}
+ - anchor = I18n.t("layout.footer.#{section}_anchor", default: "")
+ %a{:href => "#{link}#{anchor}", :target => "_blank"}
= t("layout.footer."+section.to_s)
diff --git a/app/views/mappings/_concept_mappings.html.haml b/app/views/mappings/_concept_mappings.html.haml
index c71fe9066b..2a6b56b29e 100644
--- a/app/views/mappings/_concept_mappings.html.haml
+++ b/app/views/mappings/_concept_mappings.html.haml
@@ -2,17 +2,16 @@
= "#{@mappings.size}"
= turbo_frame_tag @type.eql?('modal') ? 'application_modal_content' : 'concept_mappings' do
- %div{:style => "padding: 1%; width: 98%"}
+ %div.p-1
- if session[:user].nil?
= link_to "Create New Mapping", "/login?redirect=/ontologies/#{@ontology.acronym}/?p=classes&t=mappings&conceptid=#{escape(@concept.id)}", :method => :get, :class => "btn btn-default mb-3"
- else
- = link_to_modal("Create New Mapping",
- new_mapping_path(ontology_from: "#{@ontology.id}", conceptid_from: "#{@concept.id}"),
- id: "new_mapping_btn",
- role: "button",
- class: "btn btn-default mb-3",
- data: { show_modal_title_value: "Create a new mapping for #{@concept.prefLabel}" },
- )
+ %div{style: 'width: 250px; margin: 0 0 10px 0;'}
+ = link_to_modal(nil,
+ new_mapping_path(ontology_from: "#{@ontology.id}", conceptid_from: "#{@concept.id}"),
+ data: { show_modal_title_value: "Create a new mapping for #{@concept.prefLabel}" },
+ ) do
+ = render Buttons::RegularButtonComponent.new(id:'new_mapping_btn' , value:t('mappings.create_new_mapping'), variant: 'secondary', size: 'slim', state: 'no-anim')
#mapping_details
- = render :partial => '/mappings/mapping_table'
\ No newline at end of file
+ = render :partial => '/mappings/mapping_table'
diff --git a/app/views/mappings/_mapping_table.html.haml b/app/views/mappings/_mapping_table.html.haml
index 1d91b3b5bb..20efcd92d5 100644
--- a/app/views/mappings/_mapping_table.html.haml
+++ b/app/views/mappings/_mapping_table.html.haml
@@ -1,16 +1,14 @@
= check_box_tag "delete_mappings_permission", @delete_mapping_permission, @delete_mapping_permission, style: "display: none;"
%div#concept_mappings_tables_div
= render_alerts_container(MappingsController)
- %table#concept_mappings_table.table-content-stripped.table-content{width: "100%", style:'word-break: break-word'}
+ %table#concept_mappings_table.table-content-stripped.table-content.table-mini
%thead
%tr
%th= t("mappings.mapping_table.mapping_to")
- %th{width: "30%"}= t("mappings.count.ontology")
- %th= t("mappings.mapping_table.relations")
- %th= t("mappings.mapping_table.source")
+ %th= t("mappings.count.ontology")
%th= t("mappings.mapping_table.type")
- - if current_user_admin?
- %th{:class => 'delete_mappings_column'}= t("mappings.mapping_table.actions")
+ - if current_user_admin? && !@mappings.all? { |obj| !obj.source.eql?('REST') }
+ %th{:class => 'delete_mappings_column'}
%tbody#concept_mappings_table_content
- @mappings.each do |map|
= render partial: 'mappings/show_line' , locals: {map: map, concept: @concept}
@@ -19,4 +17,4 @@
:javascript
jQuery(document).ready(function(){
ajax_process_init();
- })
\ No newline at end of file
+ })
diff --git a/app/views/mappings/_show_line.html.haml b/app/views/mappings/_show_line.html.haml
index bbfab2b78e..4c94555c4d 100644
--- a/app/views/mappings/_show_line.html.haml
+++ b/app/views/mappings/_show_line.html.haml
@@ -1,56 +1,23 @@
-- process = map.process || {}
-- source = "#{map.source} #{process[:source_name]}"
-- relations = process[:relation]&.each { |relation| get_prefixed_uri(relation)}
+- type, type_tooltip = mapping_type_tooltip(map)
+- cls_link, ont_link, source_tooltip = mapping_links(map, concept)
- map_id = map.id.to_s.split("/").last
-- target_concept = map.classes.select {|target_concept| target_concept.id != concept.id && target_concept.links['ontology'] != concept.links['ontology']}.first || map.classes.last
-- if inter_portal_mapping?(target_concept)
- - cls_link = ajax_to_inter_portal_cls(target_concept)
- - ont_name = target_concept.links['ontology']
- - ont_acronym = ont_name
- - ont_link = link_to ont_acronym , get_inter_portal_ui_link(target_concept.links['ontology'], process["name"]), target: '_blank'
- - type = 'Inter-portal'
-- elsif internal_mapping?(target_concept)
- - begin
- - ont = target_concept.explore.ontology
- - ont_name = ont.acronym
- - ont_link = link_to ont_name, ontology_path(ont_name), 'data-turbo-frame':'_top'
- - rescue
- - ont_name = target_concept.links['ontology'] || target_concept.id
- - ont_link = ont_name
- - cls_link = raw(get_link_for_cls_ajax(target_concept.id, ont_name, '_top'))
- - type = 'Internal'
-- else
- - cls_label = get_label_for_external_cls(target_concept.links["self"])
- - cls_link = raw("#{cls_label}")
- - ont_name = target_concept.links['ontology']
- - ont_link = link_to ont_name, target_concept.links['ontology'], target: "_blank"
- - type = 'External'
-
%tr.human{:id => map_id}
- %td
+ %td.mappings-table-mapping-to
= cls_link
- %td
+ %td.mappings-table-mapping-to
= ont_link
%td
- - relations&.each do |r|
- = r
- %br/
- %td
- #{source}
- - if !process.nil?
- - if translation?(process["relation"])
- %img{:src => asset_path('sifr/english_language_flag.png'), :style => "padding: 5px", :align => "right", :title => "Traduction"}
- %td
- = type
+ = render ChipButtonComponent.new(class: 'chip_button_small mr-1', text: type, tooltip:"#{source_tooltip} mapping of type #{type_tooltip}")
+
- if current_user_admin?
%td{:class => 'delete_mappings_column'}
- if map.id && !map.id.empty? && session[:user] && (session[:user].id.to_i == map.creator || session[:user].admin?) && map.source.eql?('REST')
- %div.d-flex
- = link_to_modal(t("mappings.show_line.edit_modal"),
+ %div.d-flex.mappings-table-actions
+ = link_to_modal(nil,
mapping_path(map_id, {conceptid_from: @concept.id}),
- role: "button",
- class: "btn btn-link",
+ class: 'btn btn-link p-0 mr-1',
data: { show_modal_title_value: t("mappings.show_line.edit_mapping", preflabel: @concept.prefLabel)},
- )
- = button_to t("mappings.show_line.delete_button"), CGI.unescape(mapping_path(map.id)), method: :delete, class:'btn btn-link', form: {data: { turbo: true, turbo_confirm: t("mappings.show_line.turbo_confirm"), turbo_frame: '_top'}}
\ No newline at end of file
+ ) do
+ = inline_svg_tag "edit.svg", width: '15px', height: '15px'
+ = button_to inline_svg_tag('icons/delete.svg', width: '16px', heigth: '16px'), CGI.unescape(mapping_path(map.id)), class: 'btn btn-link p-0', method: :delete, form: {data: { turbo: true, turbo_confirm: t("mappings.show_line.turbo_confirm"), turbo_frame: '_top'}}
diff --git a/app/views/ontologies/browser/_ontologies.html.haml b/app/views/ontologies/browser/_ontologies.html.haml
index bd3aed0cf5..b9ba32c32e 100644
--- a/app/views/ontologies/browser/_ontologies.html.haml
+++ b/app/views/ontologies/browser/_ontologies.html.haml
@@ -4,14 +4,24 @@
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(@federation_counts, @time)).html_safe
+
+ = alert_message_if_federation_error(@errors) 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
- .browse-empty-illustration
- %img{:src => "#{asset_path("empty-box.svg")}"}
- %p No result was found
\ No newline at end of file
+ = empty_state
diff --git a/app/views/ontologies/browser/browse.html.haml b/app/views/ontologies/browser/browse.html.haml
index d1979fd359..d6632fbc27 100644
--- a/app/views/ontologies/browser/browse.html.haml
+++ b/app/views/ontologies/browser/browse.html.haml
@@ -1,7 +1,7 @@
.browse-center
.browse-container
.container.align-alert
- - if session[:user]&.admin?
+ - if current_user_admin?
%div{style:'width: 70%;'}
= render Display::AlertComponent.new(type: 'info') do
%span.d-flex.align-items-center
@@ -33,44 +33,33 @@
%div{data: { controller: "turbo-frame history browse-filters" , "turbo-frame-url-value": "/ontologies_filter?page=1{request.original_url.split('?').last}", action: "change->browse-filters#dispatchFilterEvent changed->history#updateURL changed->turbo-frame#updateFrame"}}
.browse-sub-container
- .browse-first-row{data:{controller: "browse-filters", action: "change->browse-filters#dispatchFilterEvent changed->history#updateURL"}}
+ .browse-first-row
%div.pt-1
= upload_ontology_button
%div{style:'margin-top: 30px'}
%p.browse-filters-title= t("ontologies.filters")
- - if session[:user]&.admin?
+ - if current_user_admin?
%div.browse-filter.admin-border
- = render SwitchInputComponent.new(id:'filter-private', name:'private_only', checked: @show_private_only) do
- = t("ontologies.browser.show_private_ontology")
+ = switch_input(id:'filter-private', name:'private_only', checked: @show_private_only, label: t("ontologies.browser.show_private_ontology"))
%div.browse-filter
- = render SwitchInputComponent.new(id:'filter-views', name:'views', checked: @show_views) do
- = t("ontologies.browser.show_ontology_views")
- = render SwitchInputComponent.new(id:'filter-retired', name:'retired',checked: @show_retired) do
- = t("ontologies.browser.show_retired_ontologies")
+ = switch_input(id:'filter-views', name:'views', checked: @show_views, label: t("ontologies.browser.show_ontology_views"))
+ = switch_input(id:'filter-retired', name:'retired',checked: @show_retired , label: t("ontologies.browser.show_retired_ontologies"))
- @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]
- .d-flex.align-items-center
- - if key.eql?(:categories) || key.eql?(:groups)
- .mr-2
- = render Display::InfoTooltipComponent.new(text: browse_taxonomy_tooltip(key.to_s))
- = inline_svg_tag 'arrow-down.svg'
- .collapse{id: "browse-#{key}-filter", class: "#{values[2].positive? ? 'show': ''}"}
- .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|
- - c.count do
- %span.badge.badge-light.ml-1
- = turbo_frame_tag "count_#{key}_#{object["id"]}", busy: true
- %span.show-if-loading
- = render LoaderComponent.new(small:true)
+ %div{ id: "#{key}_filter_container", data:{controller: "browse_filters show-filter-count",
+ action: "change->show-filter-count#updateCount
+ change->browse-filters#dispatchFilterEvent"}}
+ - objects, checked_values, count = values
+ = dropdown_component(id: "browse-#{key}-filter", is_open: count.positive?) do |d|
+ - d.title { browse_filter_section_header(key: key, count: count)}
+ = browse_filter_section_body(key: key, checked_values: checked_values, objects: objects)
+
+ - if federation_enabled?
+ %div{ data:{action: "change->browse-filters#federationChange"}}
+ = dropdown_component(id: "browse-portal-filter", is_open: !request_portals.empty?) do |d|
+ - d.title { browse_filter_section_header(title: t('federation.results_from_external_portals'))}
+ .px-1.browse-federation-input-chips
+ = federation_input_chips(name: "portals")
.browse-second-row
.browse-search-bar
@@ -79,11 +68,10 @@
.browse-search-filters
%select#format.browse-format-filter{:name => "format"}
= options_for_select(@formats, @selected_format)
- %select#Sort_by.browse-sort-by-filter{:name => "Sort_by"}
+ %select#Sort_by.browse-sort-by-filter{name: "Sort_by", 'data-browse-filters-target': "sort"}
= options_for_select(@sorts_options, @sort_by)
.browse-ontologies
= 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
- ontologies_browse_skeleton
-
diff --git a/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml b/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml
index 2aeafa23df..9bd25a770a 100644
--- a/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml
+++ b/app/views/ontologies/ontologies_selector/ontologies_selector_results.html.haml
@@ -2,7 +2,7 @@
.ontologies-selector-results
.horizontal-line
.results-number
- = t("ontologies.showing_ontologies_size", ontologies_size: @ontologies.length, analytics_size: @total_ontologies_number)
+ = t("ontologies.showing_ontologies_size", ontologies_size: @ontologies.length, analytics_size: @total_ontologies_number, portals: portal_name)
%span.select-all{'data-action': 'click->ontologies-selector#selectall'}
= t('ontologies_selector.select_all')
.ontologies
diff --git a/app/views/ontologies/sections/_collections.html.haml b/app/views/ontologies/sections/_collections.html.haml
index 280c647603..9463602928 100644
--- a/app/views/ontologies/sections/_collections.html.haml
+++ b/app/views/ontologies/sections/_collections.html.haml
@@ -1,17 +1,15 @@
= render TurboFrameComponent.new(id: "collections", data: {"turbo-frame-target": "frame"} ) do
- - if no_collections?
- = no_collections_alert
- - else
- %div.ont-collections{data:{controller: 'container-splitter'}}
- %div#collectionsTree.card.sidebar{data:{'container-splitter-target': 'container'}}
+ %div.ont-collections{data:{controller: 'container-splitter'}}
+ %div#collectionsTree.card.sidebar{data:{'container-splitter-target': 'container'}}
+ - if no_collections?
+ = no_collections_alert
+ - else
= tree_container_component(id: "collections_sorted_list_view-page-1",
placeholder: t('ontologies.sections.collections_search_placeholder', acronym: @ontology.acronym),
frame_url: "/ontologies/#{@ontology.acronym}/collections",
tree_url: "/ontologies/#{@ontology.acronym}/collections?#{request.original_url.split('?')[1]}")
-
- %div#collection_contents{data:{'container-splitter-target': 'container'}}
- = render TurboFrameComponent.new(id: 'collection') do
- - if @collection
- = render partial: 'collections/collection', locals: {collection: @collection}
+ %div#collection_contents{data:{'container-splitter-target': 'container'}}
+ = render TurboFrameComponent.new(id: 'collection') do
+ = render partial: 'collections/show'
diff --git a/app/views/ontologies/sections/_metadata.html.haml b/app/views/ontologies/sections/_metadata.html.haml
index 69aadbc570..be2083e5cb 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/app/views/ontologies/sections/_schemes.html.haml b/app/views/ontologies/sections/_schemes.html.haml
index aa01c54aa4..fc91ee21e7 100644
--- a/app/views/ontologies/sections/_schemes.html.haml
+++ b/app/views/ontologies/sections/_schemes.html.haml
@@ -11,7 +11,7 @@
%div#scheme_contents{data:{'container-splitter-target': 'container'}}
= render TurboFrameComponent.new(id:'scheme') do
- = render partial: 'schemes/scheme', locals: {scheme: @scheme}
+ = render partial: 'schemes/show'
diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml
index cd3eea4235..55bdb61edc 100644
--- a/app/views/projects/_form.html.haml
+++ b/app/views/projects/_form.html.haml
@@ -53,5 +53,5 @@
%div#ontology_picker_project{style: "padding-top: 2em;"}
- selected_ontologies = @project.ontologyUsed && @project.ontologyUsed.map {|id| id.split('/').last } || []
- locals = { sel_text: t('projects.form.select_ontologies'), selected_ontologies: selected_ontologies, form_object: :project, form_attribute: "ontologyUsed" }
- = ontologies_selector(id:'projects_page_ontologies_selector' ,name: 'ontologies')
+ = ontologies_selector(id:'projects_page_ontologies_selector' ,name: 'project[ontologyUsed][]', selected: @usedOntologies)
diff --git a/app/views/properties/_show.html.haml b/app/views/properties/_show.html.haml
index f74964f85e..d6ea4ce531 100644
--- a/app/views/properties/_show.html.haml
+++ b/app/views/properties/_show.html.haml
@@ -1,18 +1,13 @@
-= render TurboFrameComponent.new(id: 'property_show', data: {"turbo-frame-target": "frame"}) do
- - if @property
- - if @property.errors
- = render Display::AlertComponent.new(type:'info', message: @property.errors.join)
- - else
- - properties = LinkedData::Client::Models::Property.properties_to_hash(@property).first
- = render ConceptDetailsComponent.new(id:'property-details', acronym: @acronym, concept_id: @property.id,
- properties: @property.properties,
- top_keys: [],
- bottom_keys: properties.keys.map(&:to_s),
- exclude_keys: []) do |c|
- - c.header(stripped: true) do |t|
- - t.add_row({th: t('properties.id')}, {td: link_to_with_actions(@property.id, acronym: @acronym)})
- - t.add_row({th: t('properties.type')}, {td: @property.type })
- - t.add_row({th: t('properties.preferred_name')}, {td: display_in_multiple_languages(@property.label)}) unless @property.label.blank?
- - t.add_row({th: t('properties.definitions')}, {td: display_in_multiple_languages(@property.definition)}) unless @property.definition.blank?
- - t.add_row({th: t('properties.domain')}, {td: get_link_for_cls_ajax(@property.domain, @acronym, '_top')}) unless @property.domain.blank?
- - t.add_row({th: t('properties.range')}, {td: get_link_for_cls_ajax(@property.range, @acronym, '_top')}) unless @property.range.blank?
+= ontology_object_details_component(frame_id: "property_show", ontology_id: @acronym, objects_title: "properties", object: @property) do
+ = render ConceptDetailsComponent.new(id:'property-details', acronym: @acronym, concept_id: @property.id,
+ properties: @property.properties,
+ top_keys: [],
+ bottom_keys: LinkedData::Client::Models::Property.properties_to_hash(@property).first.keys.map(&:to_s),
+ exclude_keys: []) do |c|
+ - c.header(stripped: true) do |t|
+ - t.add_row({th: t('properties.id')}, {td: link_to_with_actions(@property.id, acronym: @acronym)})
+ - t.add_row({th: t('properties.type')}, {td: @property.type })
+ - t.add_row({th: t('properties.preferred_name')}, {td: display_in_multiple_languages(@property.label)}) unless @property.label.blank?
+ - t.add_row({th: t('properties.definitions')}, {td: display_in_multiple_languages(@property.definition)}) unless @property.definition.blank?
+ - t.add_row({th: t('properties.domain')}, {td: get_link_for_cls_ajax(@property.domain, @acronym, '_top')}) unless @property.domain.blank?
+ - t.add_row({th: t('properties.range')}, {td: get_link_for_cls_ajax(@property.range, @acronym, '_top')}) unless @property.range.blank?
\ No newline at end of file
diff --git a/app/views/recommender/index.html.haml b/app/views/recommender/index.html.haml
index 5ac33ba53c..57df61d9db 100644
--- a/app/views/recommender/index.html.haml
+++ b/app/views/recommender/index.html.haml
@@ -78,7 +78,7 @@
- btn.icon_left do
= inline_svg_tag "edit.svg"
- if @results && @results.empty?
- = empty_state(t('no_result_was_found'))
+ = empty_state
- unless @results.nil? || @results.empty?
.recommender-page-results
.title
@@ -122,5 +122,3 @@
= render Buttons::RegularButtonComponent.new(id:'recommender_go_annotator', value: t('recommender.call_annotator'), variant: "secondary", href: "/annotator?text=#{params[:input]}&ontologies=#{params[:ontologies]}", size: "slim", target: '_blank', state: "regular") do |btn|
- btn.icon_right do
= inline_svg_tag "arrow-right-outlined.svg"
-
-
diff --git a/app/views/schemes/_scheme.html.haml b/app/views/schemes/_scheme.html.haml
deleted file mode 100644
index 5cfac8c316..0000000000
--- a/app/views/schemes/_scheme.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-= turbo_frame_tag 'scheme' do
- - if @scheme && !@scheme.empty?
- = render ConceptDetailsComponent.new(id:'scheme-label', acronym: @ontology.acronym, concept_id: @scheme.id,
- properties: scheme.properties,
- top_keys: %w[description comment],
- bottom_keys: %w[disjoint subclass is_a has_part],
- exclude_keys: []) do |c|
- - c.header(stripped: true) do |t|
- - t.add_row({th: t('schemes.id')} , {td: link_to_with_actions(scheme["@id"], acronym: @ontology.acronym)})
- - t.add_row({th: t('schemes.preferred_name')} , {td: display_in_multiple_languages(get_scheme_label(scheme))})
- - t.add_row({th: t('schemes.type')} , {td: scheme["@type"]})
-
-
diff --git a/app/views/schemes/_show.html.haml b/app/views/schemes/_show.html.haml
new file mode 100644
index 0000000000..b0af055f4b
--- /dev/null
+++ b/app/views/schemes/_show.html.haml
@@ -0,0 +1,11 @@
+= ontology_object_details_component(frame_id: "scheme", ontology_id: @ontology.acronym, objects_title: "schemes", object: @scheme) do
+ = render ConceptDetailsComponent.new(id:'scheme-label', acronym: @ontology.acronym, concept_id: @scheme.id,
+ properties: @scheme.properties,
+ top_keys: %w[description comment],
+ bottom_keys: %w[disjoint subclass is_a has_part],
+ exclude_keys: []) do |c|
+ - c.header(stripped: true) do |t|
+ - t.add_row({th: t('schemes.id')} , {td: link_to_with_actions(@scheme["@id"], acronym: @ontology.acronym)})
+ - t.add_row({th: t('schemes.preferred_name')} , {td: display_in_multiple_languages(get_scheme_label(@scheme))})
+ - t.add_row({th: t('schemes.type')} , {td: @scheme["@type"]})
+
diff --git a/app/views/schemes/show.html.haml b/app/views/schemes/show.html.haml
deleted file mode 100644
index 79eb63ac8f..0000000000
--- a/app/views/schemes/show.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= render partial: 'scheme', locals: {scheme: @scheme}
\ No newline at end of file
diff --git a/app/views/search/index.html.haml b/app/views/search/index.html.haml
index 28ecbf2eed..a87850f86e 100644
--- a/app/views/search/index.html.haml
+++ b/app/views/search/index.html.haml
@@ -14,8 +14,13 @@
.title
= t("search.advanced_options.ontologies")
.field
- = ontologies_selector(id:'search_page_ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(','))
-
+ = ontologies_selector(id:'search_page_ontologies' ,name: 'ontologies[]', selected: params[:ontologies]&.split(','), ontologies: onts_for_select(include_views: true))
+ - if federation_enabled?
+ .filter-container
+ .title
+ = t('federation.results_from_external_portals')
+ .field.d-flex
+ = federation_input_chips(name: "portals[]")
.right
.filter-container
.title
@@ -35,38 +40,37 @@
.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', results_size: @search_results.length, portals: request_portals_names(@federation_counts, @time)).html_safe
%div.d-flex
- .search-page-json.mx-4.mt-1
- = search_json_link
+ .search-page-advanced-button.mr-4{class: @search_results.blank? ? 'd-none' : ''}
+ %a.d-flex{href: @json_url, target: '_blank'}
+ = text_with_icon(text: "API JSON", icon: 'json.svg')
+
.search-page-advanced-button.show-options{class: "#{@advanced_options_open ? 'd-none' : ''}",'data': {'action': 'click->reveal-component#show', 'reveal-component-target': 'showButton'}}
- .icon
- =inline_svg_tag 'icons/settings.svg'
- .text
- = t('search.show_advanced_options')
+ = text_with_icon(text: t('search.show_advanced_options'), icon: 'icons/settings.svg')
+
.search-page-advanced-button.hide-options{class: "#{@advanced_options_open ? '' : 'd-none'}", 'data': {'action': 'click->reveal-component#hide', 'reveal-component-target': 'hideButton'}}
- .icon
- =inline_svg_tag 'icons/hide.svg'
- .text
- = t('search.hide_advanced_options')
- - if @search_results
- .search-page-results-container
- - number = 0
- - @search_results.each do |result|
- .search-page-result-element
- - number = number + 1
- - descendants = result[:descendants]
- - reuses = result[:reuses]
- - result[:root][:number] = number
- = render Display::SearchResultComponent.new(result[:root]) do |c|
- - descendants.each { |d| c.subresult(d.merge(is_sub_component: true))}
- - reuses.each do |r|
- - number = number + 1
- - c.reuse(r[:root].merge(is_sub_component: true, number: number)) do |b|
- - r[:descendants].each { |dd| b.subresult(dd.merge(is_sub_component: true))}
+ = text_with_icon(text: t('search.hide_advanced_options'), icon: 'icons/hide.svg')
+
+
+
+
+ = alert_message_if_federation_error(@federation_errors) {@federation_errors}
+
+ .search-page-results-container
+ - number = 0
+ - @search_results.each do |result|
+ .search-page-result-element
+ - number = number + 1
+ - descendants = result[:descendants]
+ - reuses = result[:reuses]
+ - result[:root][:number] = number
+ = render Display::SearchResultComponent.new(result[:root]) do |c|
+ - descendants.each { |d| c.subresult(d.merge(is_sub_component: true))}
+ - reuses.each do |r|
+ - number = number + 1
+ - c.reuse(r[:root].merge(is_sub_component: true, number: number)) do |b|
+ - r[:descendants].each { |dd| b.subresult(dd.merge(is_sub_component: true))}
- if @search_results.empty? && !@search_query.empty?
- .browse-empty-illustration
- %img{:src => "#{asset_path("empty-box.svg")}"}
- %p No result was found
-
\ No newline at end of file
+ = empty_state
diff --git a/config/bioportal_config_env.rb.sample b/config/bioportal_config_env.rb.sample
index 277b972015..90dad17f3c 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"
@@ -213,51 +213,81 @@ $HOME_PAGE_LOGOS = [
$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: '#3CB371',
+ 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/',
+ api: 'https://data.bioportal.lirmm.fr/',
+ apikey: '1de0a270-29c5-4dda-b043-7c3580628cd5',
color: '#74a9cb',
- portal: 'SIFR BioPortal',
- link: 'https://bioportal.lirmm.fr/'
+ 'light-color': '#E9F2FA',
},
{
- color: '#0d508a',
- portal: 'EcoPortal',
- link: 'https://ecoportal.lifewatch.eu/'
+ name: 'EcoPortal',
+ ui: 'https://ecoportal.lifewatch.eu/',
+ api: 'https://data.ecoportal.lifewatch.eu/',
+ apikey: "43a437ba-a437-4bf0-affd-ab520e584719",
+ color: '#2076C9',
+ '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',
+ api: 'https://data.industryportal.enit.fr/',
+ apikey: '019adb70-1d64-41b7-8f6e-8f7e5eb54942',
color: '#1c0f5d',
- portal: 'IndustryPortal',
- link: 'http://industryportal.enit.fr'
+ 'light-color': '#F0F5F6',
},
{
- color: '#1e2251',
- portal: 'EarthPortal',
- link: 'https://earthportal.eu/'
+ name: 'EarthPortal',
+ ui: 'https://earthportal.eu/',
+ api: 'https://data.earthportal.eu/',
+ apikey: "c9147279-954f-41bd-b068-da9b0c441288",
+ color: '#404696',
+ 'light-color': '#F0F5F6'
},
{
- color: '#33691B',
- portal: 'BiodivPortal',
- link: 'https://biodivportal.gfbio.org/'
+ 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: '#349696',
+ 'light-color': '#EBF5F5',
}
]
+
+
$ONTOPORTAL_WEBSITE_LINK = "https://ontoportal.org/"
$ONTOPORTAL_GITHUB_REPO = "https://github.com/ontoportal"
@@ -287,8 +317,8 @@ $FOOTER_LINKS = {
},
agreements: {
terms: $TERMS_AND_CONDITIONS_LINK,
- privacy_policy: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/terms-conditions-naDsDo2Zxq#h-privacy-policy",
- legal_notices: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/terms-conditions-naDsDo2Zxq#h-legal-notice"
+ privacy_policy: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/terms-conditions-naDsDo2Zxq",
+ legal_notices: "https://doc.jonquetlab.lirmm.fr/share/e6158eda-c109-4385-852c-51a42de9a412/doc/terms-conditions-naDsDo2Zxq"
},
about: {
about_us: "https://github.com/agroportal/project-management",
diff --git a/config/initializers/ontologies_api_client.rb b/config/initializers/ontologies_api_client.rb
index ca8f07e9eb..068da53829 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 fd04ba863c..a63e3500eb 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -303,6 +303,7 @@ en:
and fungi, in an attempt to replace existing methods of chemical control and avoid extensive use of fungicides, which often lead to resistance in plant pathogens.
In agriculture, plant growth-promoting and biocontrol microorganisms have emerged as safe alternatives to chemical pesticides. Streptomyces spp. and their
metabolites may have great potential as excellent agents for controlling various fungal and bacterial phytopathogens.
+ select_ontologies: Select ontologies
concepts:
error_valid_concept: "Error: You must provide a valid concept id"
missing_roots: Missing roots
@@ -518,8 +519,11 @@ en:
issues_and_requests: Issues and Requests
agreements: Legal
terms: Terms and Conditions
+ terms_anchor: "#h-agroportal-terms-and-conditions-english"
privacy_policy: Privacy Policy
+ privacy_policy_anchor: "#h-privacy-policy"
legal_notices: Legal Notices
+ legal_notices_anchor: "#h-legal-notice"
cite_us: Cite Us
acknowledgments: Acknowledgments
about: About
@@ -568,12 +572,19 @@ en:
date: Date
ontology_visits: Ontology visits
mappings:
+ create_new_mapping: Create new mapping
all: All
description: Dive into an overview of the mappings in the bubble view, efficiently locate a specific ontology in the table view or upload your own mappings.
tabs:
bubble_view: Bubbles view
table_view: Table view
upload_mappings: Upload mappings
+ types_description:
+ cui: Created between 2 concepts that have the same CUI (Concept Unique Identifiers)
+ loom: Lexical mappings created between 2 concepts with very similar labels (preferred name)
+ rest: A mapping added by a user using the REST API (or the UI, which is calling the API to create it)
+ same_uri: Created between 2 concepts with the same URI.
+ skos: Mappings based on SKOS relationships, (e.g. skos:exactMatch or skos:closeMatch)
filter_ontologies: Filter ontologies in the bubble view
filter_bubbles: Filter bubbles
external_mappings: "External Mappings (%{number_with_delimiter})"
@@ -917,8 +928,7 @@ en:
classes_with_definitions: Classes with definitions
show_advanced_options: Show options
hide_advanced_options: Hide options
- match_in: Match in
- ontologies: ontologies
+ match_in: Match in %{results_size} ontologies from %{portals}
result_component:
details: Details
visualize: Vizualize
@@ -1162,7 +1172,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
@@ -1493,7 +1503,13 @@ en:
paragraph2: "The cookies are functional and non-optional. By staying on %{portal}, you acknowledge the information was delivered to you."
accept_button: "Accept"
privacy_link: "Privacy policy"
+ privacy_policy_anchor: "#h-privacy-policy"
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
+ not_responding: is not responding.
+ check_status: Checking %{portal} availability
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index f537fc23ad..d0969e5795 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -305,7 +305,7 @@ fr:
et aériennes, dans le but de remplacer les méthodes actuelles de contrôle chimique et d'éviter l'utilisation extensive de fongicides, qui conduisent souvent à une résistance chez les pathogènes des plantes.
En agriculture, les microorganismes promoteurs de croissance des plantes et de biocontrôle ont émergé comme des alternatives sûres aux pesticides chimiques. Les espèces de Streptomyces et leurs
métabolites peuvent avoir un grand potentiel en tant qu'agents excellents pour contrôler divers phytopathogènes fongiques et bactériens.
-
+ select_ontologies: Sélectionner des ontologies
concepts:
error_valid_concept: "Erreur : Vous devez fournir un identifiant de concept valide"
missing_roots: Racines manquantes
@@ -524,8 +524,11 @@ fr:
issues_and_requests: Problèmes et demandes
agreements: Légal
terms: Termes et conditions
+ terms_anchor: "#h-conditions-generales-dutilisation-dagroportal-francais"
privacy_policy: Politique de confidentialité
+ privacy_policy_anchor: "#h-vie-privee"
legal_notices: Mentions légales
+ legal_notices_anchor: "#h-mentions-legales"
cite_us: Citez-nous
acknowledgments: Remerciements
about: À propos
@@ -577,12 +580,20 @@ fr:
ontology_visits: Visites d'ontologies
mappings:
+ create_new_mapping: Créer un nouveau mapping
all: Tous
description: Plongez dans une vue d'ensemble des mappings dans la vue en bulles, localisez efficacement une ontologie spécifique dans la vue en tableau ou téléchargez vos propres mappings.
tabs:
bubble_view: Vue en bulles
table_view: Vue en tableau
upload_mappings: Télécharger des mappings
+ types_description:
+ cui: Créé entre 2 concepts ayant le même CUI (Identifiants Uniques de Concepts)
+ loom: Mappings lexicaux créés entre 2 concepts avec des libellés très similaires (nom préféré)
+ rest: Un mapping ajouté par un utilisateur via l'API REST (ou l'interface utilisateur, qui appelle l'API pour le créer)
+ same_uri: Créé entre 2 concepts ayant la même URI.
+ skos: Mapping basés sur des relations SKOS (par exemple, skos:exactMatch ou skos:closeMatch)
+
filter_ontologies: Filtrer les ontologies dans la vue en bulles
filter_bubbles: Filtrer les bulles
external_mappings: "Mappings externes (%{number_with_delimiter})"
@@ -934,8 +945,7 @@ fr:
classes_with_definitions: Classes avec définitions
show_advanced_options: Afficher les options
hide_advanced_options: Cacher les options
- match_in: Correspondance dans
- ontologies: ontologies
+ match_in: Correspondance dans %{results_size} ontologies de %{portals}
result_component:
details: Détails
visualize: Visualiser
@@ -1188,7 +1198,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
@@ -1532,7 +1542,12 @@ fr:
paragraph2: "Les cookies sont fonctionnels et non facultatifs. En restant sur %{portal}, vous reconnaissez que les informations vous ont été transmises."
accept_button: "Accepter"
privacy_link: "Vie privé"
+ privacy_policy_anchor: "#h-vie-privee"
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
+ not_responding: ne répond pas.
+ check_status: Vérification de la disponibilité de %{portal}
diff --git a/config/routes.rb b/config/routes.rb
index f31620d09a..b3760280fb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -8,6 +8,7 @@
get 'auth/:provider/callback', to: 'login#create_omniauth'
get 'locale/:language', to: 'language#set_locale_language'
get 'metadata_export/index'
+ get '/config', to: 'home#portal_config'
get '/notes/new_comment', to: 'notes#new_comment'
get '/notes/new_proposal', to: 'notes#new_proposal'
@@ -130,6 +131,7 @@
end
get '' => 'home#index'
+ get 'status/:portal_name', to: 'home#federation_portals_status'
match 'sparql_proxy', to: 'admin#sparql_endpoint', via: [:get, :post]
diff --git a/test/helpers/application_test_helpers.rb b/test/helpers/application_test_helpers.rb
index 975c220eac..28530711f7 100644
--- a/test/helpers/application_test_helpers.rb
+++ b/test/helpers/application_test_helpers.rb
@@ -16,17 +16,27 @@ module Users
def sign_in_as(username)
user = fixtures(:users)[username]
logged_in_user = LinkedData::Client::Models::User.authenticate(user.username, user.password)
- if logged_in_user && !logged_in_user.errors
- logged_in_user = create_user(user)
- end
+ logged_in_user = create_user(user) if logged_in_user && !logged_in_user.errors
logged_in_user
end
def create_user(user, admin: false)
- admin_user = LinkedData::Client::Models::User.authenticate('admin', 'password') if admin
+ admin_user = LinkedData::Client::Models::User.authenticate('admin', 'password')
existent_user = LinkedData::Client::Models::User.find_by_username(user.username).first
- existent_user.delete if existent_user
+ conn = Faraday.new(url: LinkedData::Client.settings.rest_url) do |faraday|
+ faraday.request :url_encoded
+ faraday.response :logger
+ faraday.adapter Faraday.default_adapter
+ faraday.headers = {
+ "Accept" => "application/json",
+ "Authorization" => "apikey token=#{admin_user.apikey}",
+ "User-Agent" => "NCBO API Ruby Client v0.1.0"
+ }
+
+ end
+
+ conn.delete("/users/#{user.username}") if existent_user
values = user.to_h
values[:role] = ["ADMINISTRATOR"] if admin
@@ -34,17 +44,6 @@ def create_user(user, admin: false)
if admin
# Overwrite the normal ".save" to accept creating admin user
- conn = Faraday.new(url: LinkedData::Client.settings.rest_url) do |faraday|
- faraday.request :url_encoded
- faraday.response :logger
- faraday.adapter Faraday.default_adapter
- faraday.headers = {
- "Accept" => "application/json",
- "Authorization" => "apikey token=#{admin_user.apikey}",
- "User-Agent" => "NCBO API Ruby Client v0.1.0"
- }
-
- end
conn.post(existent_user.class.collection_path, existent_user.to_hash.to_json, 'Content-Type' => 'application/json')
else
existent_user.save
@@ -61,7 +60,22 @@ def delete_users(users = LinkedData::Client::Models::User.all)
end
def delete_user(user)
- LinkedData::Client::Models::User.find_by_username(user.username).first&.delete
+ admin_user = LinkedData::Client::Models::User.authenticate('admin', 'password')
+ existent_user = LinkedData::Client::Models::User.find_by_username(user.username).first
+
+ conn = Faraday.new(url: LinkedData::Client.settings.rest_url) do |faraday|
+ faraday.request :url_encoded
+ faraday.response :logger
+ faraday.adapter Faraday.default_adapter
+ faraday.headers = {
+ "Accept" => "application/json",
+ "Authorization" => "apikey token=#{admin_user.apikey}",
+ "User-Agent" => "NCBO API Ruby Client v0.1.0"
+ }
+
+ end
+
+ conn.delete("/users/#{user.username}") if existent_user
end
end
@@ -133,4 +147,4 @@ def delete_agents(agents = LinkedData::Client::Models::Agent.all)
Array(agents).each { |g| g.delete }
end
end
-end
\ No newline at end of file
+end
diff --git a/test/system/login_flows_test.rb b/test/system/login_flows_test.rb
index d6ff0deb49..8bed283d20 100644
--- a/test/system/login_flows_test.rb
+++ b/test/system/login_flows_test.rb
@@ -22,8 +22,6 @@ class LoginFlowsTest < ApplicationSystemTestCase
new_user = @user_john
delete_user(new_user)
- LinkedData::Client::Models::User.find_by_username(new_user.username).first&.delete
-
fill_in 'user_firstName', with: new_user.firstName
fill_in 'user_lastName', with: new_user.lastName
fill_in 'user_username', with: new_user.username
diff --git a/test/system/submission_flows_test.rb b/test/system/submission_flows_test.rb
index 6045308d04..a0c06a4f47 100644
--- a/test/system/submission_flows_test.rb
+++ b/test/system/submission_flows_test.rb
@@ -46,7 +46,6 @@ class SubmissionFlowsTest < ApplicationSystemTestCase
assert_text cat.acronym.titleize
end
-
assert_text @new_submission.URI
assert_text @new_submission.description
assert_text @new_submission.pullLocation
@@ -103,7 +102,6 @@ class SubmissionFlowsTest < ApplicationSystemTestCase
click_on "Licensing"
submission_licensing_edit_fill(ontology_2, submission_2)
-
# Persons and organizations tab
click_on "Persons and organizations"
submission_agent_edit_fill(submission_2)
@@ -148,7 +146,10 @@ class SubmissionFlowsTest < ApplicationSystemTestCase
assert_text submission_2.URI
assert_text submission_2.versionIRI
+
+ wait_for '.submission-status'
assert_selector '.submission-status', text: submission_2.version
+
assert_selector ".flag-icon-fr" # todo fix this
submission_2.identifier.each do |id|
assert_text id
@@ -258,7 +259,6 @@ class SubmissionFlowsTest < ApplicationSystemTestCase
assert_text "rdfs"
assert_text "dct"
-
open_dropdown "#configuration"
submission_2.keyClasses.each do |key|
@@ -341,10 +341,8 @@ class SubmissionFlowsTest < ApplicationSystemTestCase
sleep 0.5
click_button 'Back'
-
fill_ontology(ontology_2, submission_2, add_submission: true)
-
assert_selector 'h2', text: 'Ontology submitted successfully!'
click_on current_url.gsub("/ontologies/success/#{existent_ontology.acronym}", '') + ontology_path(existent_ontology.acronym)
@@ -362,7 +360,6 @@ class SubmissionFlowsTest < ApplicationSystemTestCase
assert_text submission_2.description
assert_text submission_2.pullLocation
-
# check
assert_selector '.fas.fa-key' if submission_2.status.eql?('private')
@@ -380,7 +377,6 @@ class SubmissionFlowsTest < ApplicationSystemTestCase
assert_text group.name
end
-
open_dropdown "#dates"
assert_date submission_2.modificationDate
assert_date existent_submission.released
@@ -483,7 +479,6 @@ def submission_agent_edit_fill(submission)
list_inputs "#submissioncontact_from_group_input", "submission[contact]", submission.contact
-
agent1 = fixtures(:agents)[:agent1]
agent2 = fixtures(:agents)[:agent2]
@@ -624,7 +619,6 @@ def fill_ontology(new_ontology, new_submission, add_submission: false)
list_checks new_ontology.hasDomain.map(&:acronym), @categories.map(&:acronym)
list_checks new_ontology.group.map(&:acronym), @groups.map(&:acronym)
-
click_button 'Next'
# Page 2
@@ -644,9 +638,9 @@ def fill_ontology(new_ontology, new_submission, add_submission: false)
# Page 3
if add_submission
- date_picker_fill_in 'submission[modificationDate]', new_submission.modificationDate
+ date_picker_fill_in 'submission[modificationDate]', new_submission.modificationDate
else
- date_picker_fill_in 'submission[released]', new_submission.released
+ date_picker_fill_in 'submission[released]', new_submission.released
end
list_inputs "#submissioncontact_from_group_input", "submission[contact]", new_submission.contact