Skip to content

Commit

Permalink
Feature: update concept mappings table UI (#798)
Browse files Browse the repository at this point in the history
* fix mappings table style

* hide relations column when it's completely empty

* use icons and Abbreviations types and sources

* hide actions column if no mapping is of type REST

* update create new mapping button style

* remove concept mapping table padding

* remove relation column and remove Action title

* make the column width for the mapping to and ontology larger

* move some mappings helper to the mapping controller as used only there

* move the logic from the mapping table view to helpers

* fix link text component icon size

* change mappings action icons colors to primary color

* pass button component in create new mapping instead of using only the css of it

* internationalize create new mapping button text

* internationalize mapping types description

* fix interportal mapping issue when the interportal_hash is empty

* limit mapping type tooltip by 300px width

---------

Co-authored-by: Syphax <[email protected]>
  • Loading branch information
Bilelkihal and syphax-bouazzouni authored Oct 31, 2024
1 parent e616842 commit f1773c1
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 154 deletions.
25 changes: 25 additions & 0 deletions app/assets/stylesheets/mappings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
}
2 changes: 1 addition & 1 deletion app/components/link_text_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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') ? '' : "<span class='mx-1'>#{svg_icon}</span>"
"#{@text}#{extra_span}".html_safe
end
Expand Down
54 changes: 53 additions & 1 deletion app/controllers/mappings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,56 @@ def valid_values?(values)
end
errors
end
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_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
174 changes: 83 additions & 91 deletions app/helpers/mappings_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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("<a href='#{target_concept.links["self"]}' target='_blank'>#{cls_label}</a>")
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=' "
Expand All @@ -52,15 +94,15 @@ def ajax_to_inter_portal_cls(cls)

def ajax_to_internal_cls(cls)
link_to("#{cls.id}<span href='/ajax/classes/label?ontology=#{cls.links["ontology"]}&concept=#{escape(cls.id)}' class='get_via_ajax'></span>".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.
# The best way to know from which interportal instance the class came is to compare the UI url
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
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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.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]
!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(
Expand All @@ -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
Expand All @@ -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
Expand Down
17 changes: 8 additions & 9 deletions app/views/mappings/_concept_mappings.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
= render :partial => '/mappings/mapping_table'
Loading

0 comments on commit f1773c1

Please sign in to comment.