diff --git a/app/assets/images/bip-minimal.svg b/app/assets/images/bip-minimal.svg new file mode 100644 index 000000000..7579d0dec --- /dev/null +++ b/app/assets/images/bip-minimal.svg @@ -0,0 +1,22 @@ + + + diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index f2ad52044..48a5fd88e 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -48,6 +48,7 @@ @import "pages/about"; @import "pages/account"; @import "pages/activities"; +@import "pages/bip-scholar"; @import "pages/collaborations"; @import "pages/collection"; @import "pages/curators"; diff --git a/app/assets/stylesheets/pages/bip-scholar.scss b/app/assets/stylesheets/pages/bip-scholar.scss new file mode 100644 index 000000000..364658e3a --- /dev/null +++ b/app/assets/stylesheets/pages/bip-scholar.scss @@ -0,0 +1,110 @@ +.bip-scholar-embed { +margin: 6px 0; +display: block; + +.bip-scholar-badge { + * { box-sizing: border-box; } + + display: inline-flex; + align-items: center; + gap: 0; + background: #ffffff; + border: 1px solid #e0e0e0; + border-radius: 10px; + padding: 8px 14px; + box-shadow: 0 2px 8px rgba(0,0,0,0.08); + cursor: pointer; + font-family: 'Nunito', sans-serif; + position: relative; + transition: box-shadow 0.2s; + text-decoration: none !important; /* Force override global links */ + color: inherit !important; + + &:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.14); } +} + +.bip-scholar-stat { + display: flex; + flex-direction: column; + align-items: center; + padding: 0 10px; + min-width: 54px; + + & + .bip-scholar-stat { border-left: 1px solid #e8e8e8; } +} + +.bip-scholar-stat--group-sep { + border-left: 2px solid #c8c8c8 !important; + margin-left: 2px; +} + +.bip-scholar-stat-value { + font-size: 15px; + font-weight: 700; + color: #1a1a1a; + line-height: 1.2; + white-space: nowrap; +} + +.bip-scholar-stat-label { + font-size: 10px; + font-weight: 600; + color: #9e9e9e; + text-transform: uppercase; + letter-spacing: 0.03em; + margin-top: 2px; + white-space: nowrap; +} + +.bip-scholar-stat-icon { + font-size: 11px; + margin-right: 3px; + color: #757575; +} + +.bip-scholar-badge--compact { + .bip-scholar-stat { + min-width: auto; + padding: 0 8px; + } + .bip-scholar-stat-value { font-size: 14px; } + .bip-scholar-stat-icon { + font-size: 12px; + margin-right: 5px; + } +} + +.bip-scholar-logo { + display: flex; + align-items: center; + padding-right: 12px; + margin-right: 2px; + border-right: 1px solid #e8e8e8; + font-weight: bold; + color: #1a1a1a; +} + +.bip-scholar-no-profile { + display: inline-flex; + align-items: center; + gap: 7px; + background: #fafafa; + border: 1px dashed #d0d0d0; + border-radius: 10px; + padding: 7px 14px; + font-family: 'Nunito', sans-serif; + font-size: 12px; + color: #aaa; + + a { + color: #2e7d32; + font-weight: 600; + text-decoration: none; + opacity: 0.8; + &:hover { + text-decoration: underline; + opacity: 1; + } + } +} +} \ No newline at end of file diff --git a/app/helpers/bip_scholar_helper.rb b/app/helpers/bip_scholar_helper.rb new file mode 100644 index 000000000..35ca50672 --- /dev/null +++ b/app/helpers/bip_scholar_helper.rb @@ -0,0 +1,47 @@ +module BipScholarHelper + require 'net/http' + require 'cgi' + require 'json' + + def self.fetch_score(orcid) + return nil if orcid.blank? + + # Cache in Redis for 24 hours to prevent blocking TeSS web threads + Rails.cache.fetch("bip_scholar_score_#{orcid}", expires_in: 24.hours, skip_nil: true) do # `skip_nil: true` to avoid caching nil and then make the widget disappear + uri = URI("https://bip-api.imsi.athenarc.gr/scholar/scores/#{CGI.escape(orcid)}") + + # Use timeout to prevent hanging connections + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.read_timeout = 3 + http.open_timeout = 2 + + response = http.get(uri.request_uri) + + response.is_a?(Net::HTTPSuccess) ? JSON.parse(response.body) : nil + rescue StandardError => e + Rails.logger.error("BIP!Scholar API Error for #{orcid}: #{e.message}") + nil + end + end + + def format_scholar_number(num) + num.present? ? number_with_delimiter(num) : "—" + end + + def parse_work_types(data) + wt = data['work_types_num'] + if wt.is_a?(Array) + wt + elsif wt.is_a?(Hash) + [ + wt['papers'], + wt['dataset'] || wt['datasets'], + wt['software'], + wt['other'] + ] + else + [nil, nil, nil, nil] + end + end +end \ No newline at end of file diff --git a/app/views/common/_bip_scholar_infographics.html.erb b/app/views/common/_bip_scholar_infographics.html.erb new file mode 100644 index 000000000..444a78b7d --- /dev/null +++ b/app/views/common/_bip_scholar_infographics.html.erb @@ -0,0 +1,59 @@ +<% + # for the `layout` and `empty_mode` parameters, see https://github.com/athenarc/bip-plugin/tree/main + data = BipScholarHelper.fetch_score(orcid) + is_compact = local_assigns.fetch(:layout, 'default') == 'compact' # compact allows to hide most of the words, and shows only icons and numbers + empty_mode = local_assigns.fetch(:empty_mode, 'default') # empty_mode hides BIP!Scholar widget if no ORCID on BIP!Scholar is found +%> + +
\ No newline at end of file diff --git a/app/views/trainers/show.html.erb b/app/views/trainers/show.html.erb index f4627a69d..4d140a8ca 100644 --- a/app/views/trainers/show.html.erb +++ b/app/views/trainers/show.html.erb @@ -23,6 +23,9 @@