Skip to content

Commit

Permalink
Feature: Add federation portals status check (#769)
Browse files Browse the repository at this point in the history
* cache federation status call

* update chip helpers to support disabled state

* use turbo frame for federation input chips, and use federation portals status to disable non working portals

* add skelton loading animation for federation input chips

* cache federation input chips separately

* use cached federation input chips in browse page

* fix browse page federation inputs section title style

* initialize federation portals input chips in the home page (asynch in the background)

* clean federated search aggregator code

* move chip skelton to components helper

* internationalize portal is not responding message

* clean federation portal status cache method

* clean federation stimulus controller code

* display federated browse errors as warning instead of danger (orange instead of red)

* use federation portal status in home page portals configuration

* fix issue in federation portal status method

* clean portal_config_tooltip method to make it more readable

* consider the portal as down if the api of it is not present in the config file

* put federation portal status vue in a helper, and remove the html file of it

* internationalize portals status message

* extract federation_input_chips to a helper

* put init portals status in home page logic in a helper for clarity

* change home/federation_portals_status root to status/:portal_name

* use dig in federation_portal_status to ensure to not raise an exception

* move init portal status helper to federation file and fix class name

* move federation helpers from application helper to federation file and fix variables calls

* add loading state to the chips component and use it in federation status

* rename federation chip component helper name to prevent conflicts

---------

Co-authored-by: Syphax bouazzouni <[email protected]>
  • Loading branch information
Bilelkihal and syphax-bouazzouni authored Oct 22, 2024
1 parent ef836ce commit 7eb8d76
Show file tree
Hide file tree
Showing 17 changed files with 181 additions and 26 deletions.
19 changes: 19 additions & 0 deletions app/assets/stylesheets/browse.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 37 additions & 0 deletions app/assets/stylesheets/components/chips.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

}
.chips-container.disabled div label > span, .chips-container div label span:has(span.disabled){
opacity: 60%;
background-color: #f8f9fa !important;
}

Expand All @@ -43,3 +44,39 @@
.chips-container div label input[type="checkbox"]:checked ~ span .chips-check-icon{
display:unset;
}

.chips-container.loading div {
cursor: default;
opacity: 0.6;
}

.chips-container .skeleton {
width: 80px;
height: 36px;
background-color: #e0e0e0;
border-radius: 5px;
animation: shimmer 4s infinite;
position: relative;
overflow: hidden;
}

@keyframes shimmer {
0% {
background-position: -200px 0;
}
100% {
background-position: 200px 0;
}
}

.chips-container .skeleton::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0.2) 100%);
animation: shimmer 4s infinite;
border-radius: 5px;
}
12 changes: 11 additions & 1 deletion app/components/chips_component.rb
Original file line number Diff line number Diff line change
@@ -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

def disabled_class_name
@disabled ? 'disabled' : ''
end

def loading_class_name
@loading ? 'loading' : ''
end
end
20 changes: 12 additions & 8 deletions app/components/chips_component/chips_component.html.haml
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion app/controllers/concerns/search_aggregator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ def search_result_elem(class_object, ontology_acronym, title)
result
end


def ontology_name_acronym(ontologies, selected_acronym)
ontology = ontologies.select { |x| x.acronym.eql?(selected_acronym.split('/').last) }.first
"#{ontology.name} (#{ontology.acronym})" if ontology
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/home_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ 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.
Expand Down
1 change: 1 addition & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -551,4 +551,5 @@ 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, multiple: true)
end

end
18 changes: 14 additions & 4 deletions app/helpers/components_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ def alert_component(message, type: "info")
render Display::AlertComponent.new(type: type, message: message)
end

def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, &block)
def chips_component(id: , name: , label: , value: , checked: false , tooltip: nil, disabled: false, &block)
content_tag(:div, data: { controller: 'tooltip' }, title: tooltip) do
check_input(id: id, name: name, value: value, label: label, checked: checked, &block)
check_input(id: id, name: name, value: value, label: label, checked: checked, disabled: disabled, &block)
end
end

def group_chip_component(id: nil, name: , object: , checked: , value: nil, title: nil, &block)
def group_chip_component(id: nil, name: , object: , checked: , value: nil, title: nil, disabled: false, &block)
title ||= object["name"]
value ||= (object["value"] || object["acronym"] || object["id"])

chips_component(id: id || value, name: name, label: object["acronym"],
checked: checked,
value: value, tooltip: title, &block)
value: value, tooltip: title, disabled: disabled, &block)
end
alias :category_chip_component :group_chip_component

Expand Down Expand Up @@ -292,4 +292,14 @@ def form_cancel_button
end
end

def chips_skelton
content_tag(:div, class: 'chips-container loading') do
content_tag(:div) do
content_tag(:label) do
content_tag(:span, '', class: 'skeleton')
end
end
end
end

end
55 changes: 55 additions & 0 deletions app/helpers/federation_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,59 @@ def federation_external_class?(class_object)
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
end
14 changes: 12 additions & 2 deletions app/helpers/home_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,21 @@ def format_number_abbreviated(number)
end

def portal_config_tooltip(portal_name, &block)
title = render TurboFrameComponent.new(id: "portal_config_tooltip_#{portal_name&.downcase}", src: "/config?portal=#{portal_name&.downcase}", style: "width: 600px !important; max-height: 300px; overflow: scroll")
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
Expand All @@ -43,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
4 changes: 2 additions & 2 deletions app/helpers/inputs_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion app/views/home/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,12 @@
%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()
}
function submitRecommender(){
document.getElementById("recommender_submit").click()
}

5 changes: 2 additions & 3 deletions app/views/ontologies/browser/browse.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,8 @@
= render LoaderComponent.new(small:true)
.browse-federation-input-chip-container
= render DropdownContainerComponent.new(id: "browse-portal-filter", is_open: !request_portals.empty?, title: t('federation.results_from_external_portals')) do
.browse-filter-checks-container.px-1
- federated_portals.each do |key, config|
= group_chip_component(name: "portals", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '')
.px-1.browse-federation-input-chips
= federation_input_chips(name: "portals")

.browse-second-row
.browse-search-bar
Expand Down
3 changes: 1 addition & 2 deletions app/views/search/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
.title
= t('federation.results_from_external_portals')
.field.d-flex
- federated_portals.each do |key, config|
= group_chip_component(name: "portals[]", object: { "acronym" => config[:name], "value" => key }, checked: request_portals.include?(key.to_s), title: '')
= federation_input_chips(name: "portals[]")
.right
.filter-container
.title
Expand Down
3 changes: 2 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1506,4 +1506,5 @@ en:
federation:
results_from_external_portals: Results from external portals
from: from
not_responding: is not responding.
not_responding: is not responding.
check_status: Checking %{portal} availability
2 changes: 1 addition & 1 deletion config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1544,4 +1544,4 @@ fr:
results_from_external_portals: Résultats provenant de portails externes
from: de
not_responding: ne répond pas.

check_status: Vérification de la disponibilité de %{portal}
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,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]

Expand Down

0 comments on commit 7eb8d76

Please sign in to comment.