diff --git a/README.md b/README.md index cc26966f..3aae88d1 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,6 @@ See `Optional Environment Variables` for more information. - `BOOLEAN_OPTIONS`: comma separated list of values to present to testers on instances where `BOOLEAN_PICKER` feature is enabled. - `BOOLEAN_PICKER`: feature to allow users to select their preferred boolean type. If set, feature is enabled. This feature is only intended for internal team testing and should never be enabled in production (mostly because the UI is a mess more than it would cause harm). -- `FACT_PANELS_ENABLED`: Comma separated list of enabled fact panels. See `/views/results.html.erb` for implemented panels/valid options. Leave unset to disable all. - `FILTER_ACCESS_TO_FILES`: The name to use instead of "Access to files" for that filter / aggregation. - `FILTER_CONTENT_TYPE`: The name to use instead of "Content type" for that filter / aggregation. - `FILTER_CONTRIBUTOR`: The name to use instead of "Contributor" for that filter / aggregation. diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 9ed3a62f..7d25d3f3 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -19,3 +19,4 @@ @import "partials/_shared"; @import "partials/_results"; @import "partials/_typography"; +@import "partials/_suggestion-panel"; diff --git a/app/assets/stylesheets/partials/_panels.scss b/app/assets/stylesheets/partials/_panels.scss index 2f497600..170b5205 100644 --- a/app/assets/stylesheets/partials/_panels.scss +++ b/app/assets/stylesheets/partials/_panels.scss @@ -62,17 +62,6 @@ } } -.fact { - .panel { - margin-top: 0; - - .panel-heading { - margin-top: 0; - padding-top: 16.8px; - } - } -} - .ask-us { margin-top: 3rem; a { diff --git a/app/assets/stylesheets/partials/_suggestion-panel.scss b/app/assets/stylesheets/partials/_suggestion-panel.scss new file mode 100644 index 00000000..037d2480 --- /dev/null +++ b/app/assets/stylesheets/partials/_suggestion-panel.scss @@ -0,0 +1,106 @@ +/* Color Variables */ + +// Core +$blue-500: #0000FF; + +$purple-700: #990099; + +$white: #fff; +$gray-100: #F2F2F2; +$black: #111; + +@mixin focus-outline { + &:focus { + outline: 3px solid $blue-500; + } +} + +@mixin hover-transition { + transition: all .25s ease-in-out 0s; +} + +// Semantic +$color-suggestion-border: $purple-700; +$color-suggestion-accent-text: $purple-700; + +/* Suggestion Panel */ +.mitlib-suggestion-panel { + border: 4px solid $color-suggestion-border; + + display: flex; + column-gap: 24px; + + padding: 20px 24px; + position: relative; + + .panel-type { + color: $color-suggestion-accent-text; + font-size: 14px; + font-weight: 600; + margin-bottom: 8px; + } + + h3 { + font-size: 20px; + font-weight: 600; + line-height: 1.25; + margin-bottom: 8px; + } + + p { + font-size: 16px; + } + + ul.metadata { + font-size: 14px; + list-style: none inside; + padding-left: 0; + margin-bottom: 20px; + } + + // This would need to be extracted into an Icon Button in the future. + button.dismiss { + //Temporarily hiding until we can move to a prop + display: none; + + width: 48px; + height: 48px; + position: absolute; + top: 0; + right: 0; + + background-color: $white; + border: none; + color: $black; + cursor: pointer; + font-weight: 600; + + @include hover-transition; + + @include focus-outline; + + &:hover { + background-color: $gray-100; + } + } +} + +/* Button styles to be extracted into button component */ +.button.secondary { + border: 1px solid $black; + border-radius: 0; + display: inline-block; + padding: 6px 12px; + text-decoration: none; + font-size: 16px; + font-weight: 600; + + @include hover-transition; + + @include focus-outline; + + &:hover { + color: $white; + background-color: $black; + } +} \ No newline at end of file diff --git a/app/controllers/fact_controller.rb b/app/controllers/fact_controller.rb deleted file mode 100644 index 5bd4bf89..00000000 --- a/app/controllers/fact_controller.rb +++ /dev/null @@ -1,27 +0,0 @@ -class FactController < ApplicationController - layout false - - def doi - return unless params[:doi].present? - - @json = FactDoi.new.info(params[:doi]) - end - - def issn - return unless params[:issn].present? - - @json = FactIssn.new.info(params[:issn]) - end - - def isbn - return unless params[:isbn].present? - - @json = FactIsbn.new.info(params[:isbn]) - end - - def pmid - return unless params[:pmid].present? - - @json = FactPmid.new.info(params[:pmid].split.last) - end -end diff --git a/app/controllers/tacos_controller.rb b/app/controllers/tacos_controller.rb index 715e2247..bc06b1dd 100644 --- a/app/controllers/tacos_controller.rb +++ b/app/controllers/tacos_controller.rb @@ -4,6 +4,10 @@ class TacosController < ApplicationController def analyze return unless ApplicationHelper.tacos_enabled? - Tacos.analyze(params[:q]) + tacos_response = Tacos.analyze(params[:q]) + + # Suggestions return as an array but we don't want to display more than one. + # We may want to have a "priority" system in the future to determine which suggestion to show. + @suggestions = tacos_response['data']['logSearchEvent']['detectors']['suggestedResources'].first end end diff --git a/app/helpers/record_helper.rb b/app/helpers/record_helper.rb index 142ef5d6..d4b70033 100644 --- a/app/helpers/record_helper.rb +++ b/app/helpers/record_helper.rb @@ -1,11 +1,4 @@ module RecordHelper - def doi(metadata) - dois = metadata['identifiers']&.select { |id| id['kind'].downcase == 'doi' } - return unless dois.present? - - dois.first['value'] - end - def date_parse(date) return unless date.present? diff --git a/app/helpers/results_helper.rb b/app/helpers/results_helper.rb index 54e9a585..85b4860d 100644 --- a/app/helpers/results_helper.rb +++ b/app/helpers/results_helper.rb @@ -1,8 +1,4 @@ module ResultsHelper - def fact_enabled?(fact_type) - ENV.fetch('FACT_PANELS_ENABLED', false).split(',').include?(fact_type) - end - def results_summary(hits) hits.to_i >= 10_000 ? '10,000+ results' : "#{number_with_delimiter(hits)} results" end diff --git a/app/models/fact_doi.rb b/app/models/fact_doi.rb deleted file mode 100644 index 563ca1b8..00000000 --- a/app/models/fact_doi.rb +++ /dev/null @@ -1,44 +0,0 @@ -class FactDoi - def info(doi) - external_data = fetch(doi) - return if external_data == 'Error' - - metadata = extract_metadata(external_data) - metadata[:doi] = doi - metadata[:link_resolver_url] = link_resolver_url(metadata) - metadata - end - - def extract_metadata(external_data) - { - genre: external_data['genre'], - title: external_data['title'], - year: external_data['year'], - publisher: external_data['publisher'], - oa: external_data['is_oa'], - oa_status: external_data['oa_status'], - best_oa_location: external_data['best_oa_location'], - journal_issns: external_data['journal_issns'], - journal_name: external_data['journal_name'] - } - end - - def url(doi) - "https://api.unpaywall.org/v2/#{doi}?email=timdex@mit.edu" - end - - def fetch(doi) - resp = HTTP.headers(accept: 'application/json').get(url(doi)) - if resp.status == 200 - JSON.parse(resp.to_s) - else - Rails.logger.debug("Fact lookup error. DOI #{doi} detected but unpaywall returned no data") - Rails.logger.debug("URL: #{url(doi)}") - 'Error' - end - end - - def link_resolver_url(metadata) - "https://mit.primo.exlibrisgroup.com/discovery/openurl?institution=01MIT_INST&rfr_id=info:sid/mit.timdex.ui&rft.atitle=#{metadata[:title]}&rft.date=#{metadata[:year]}&rft.genre=#{metadata[:genre]}&rft.jtitle=#{metadata[:journal_name]}&rft_id=info:doi/#{metadata[:doi]}&vid=01MIT_INST:MIT" - end -end diff --git a/app/models/fact_isbn.rb b/app/models/fact_isbn.rb deleted file mode 100644 index 46596786..00000000 --- a/app/models/fact_isbn.rb +++ /dev/null @@ -1,51 +0,0 @@ -class FactIsbn - def info(isbn) - json = fetch_isbn(isbn) - return if json == 'Error' - - { - title: json['title'], - publish_date: json['publish_date'], - publishers: json['publishers'], - author_names: fetch_authors(json), - openurl: link_resolver_url(isbn) - } - end - - def base_url - 'https://openlibrary.org' - end - - def fetch_isbn(isbn) - url = [base_url, "/isbn/#{isbn}.json"].join - parse_response(url) - end - - def fetch_authors(isbn_json) - return unless isbn_json['authors'] - - authors = isbn_json['authors'].map { |a| a['key'] } - author_names = authors.map do |author| - url = [base_url, author, '.json'].join - json = parse_response(url) - json['name'] - end - author_names.join(' ; ') - end - - def parse_response(url) - resp = HTTP.headers(accept: 'application/json', 'Content-Type': 'application/json').follow.get(url) - - if resp.status == 200 - JSON.parse(resp.to_s) - else - Rails.logger.debug('Fact lookup error: openlibrary returned no data') - Rails.logger.debug("URL: #{url}") - 'Error' - end - end - - def link_resolver_url(isbn) - "https://mit.primo.exlibrisgroup.com/discovery/openurl?institution=01MIT_INST&vid=01MIT_INST:MIT&rft.isbn=#{isbn}" - end -end diff --git a/app/models/fact_issn.rb b/app/models/fact_issn.rb deleted file mode 100644 index e8431660..00000000 --- a/app/models/fact_issn.rb +++ /dev/null @@ -1,63 +0,0 @@ -class FactIssn - def info(issn) - return unless validate(issn) - - json = fetch(issn) - return if json == 'Error' - - metadata = extract_metadata(json) - metadata[:openurl] = openurl(issn) - metadata - end - - def extract_metadata(response) - { - title: response['message']['title'], - publisher: response['message']['publisher'] - } - end - - def url(issn) - "https://api.crossref.org/journals/#{issn}" - end - - def fetch(issn) - resp = HTTP.headers(accept: 'application/json').get(url(issn)) - if resp.status == 200 - JSON.parse(resp.to_s) - else - Rails.logger.debug("ISSN Lookup error. ISSN #{issn} detected but crossref returned no data") - Rails.logger.debug("URL: #{url(issn)}") - 'Error' - end - end - - def openurl(issn) - "https://mit.primo.exlibrisgroup.com/discovery/openurl?institution=01MIT_INST&rfr_id=info:sid/mit.timdex.ui&rft.issn=#{issn}&vid=01MIT_INST:MIT" - end - - def validate(candidate) - # This model is only called when the regex for an ISSN has indicated an ISSN - # of sufficient format is present - but the regex does not attempt to - # validate that the check digit in the ISSN spec is correct. This method - # does that calculation, so we can avoid sending nonsense requests to - # CrossRef or the Primo API for facially-valid ISSNs that actually are not, - # like "2015-2019". - # - # The algorithm is defined at - # https://datatracker.ietf.org/doc/html/rfc3044#section-2.2 - # An example calculation is shared at - # https://en.wikipedia.org/wiki/International_Standard_Serial_Number#Code_format - digits = candidate.gsub('-', '').chars[..6] - check_digit = candidate.last.downcase - sum = 0 - digits.each_with_index do |digit, idx| - sum += digit.to_i * (8 - idx.to_i) - end - actual_digit = 11 - sum.modulo(11) - actual_digit = 'x' if actual_digit == 10 - return true if actual_digit.to_s == check_digit.to_s - - false - end -end diff --git a/app/models/fact_pmid.rb b/app/models/fact_pmid.rb deleted file mode 100644 index dc9a7641..00000000 --- a/app/models/fact_pmid.rb +++ /dev/null @@ -1,42 +0,0 @@ -class FactPmid - def info(pmid) - xml = fetch(pmid) - return if xml == 'Error' - - data = extract_data(xml) - - if data.reject { |_k, v| v.empty? }.present? - data - else - Rails.logger.debug("Fact lookup error. PMID #{pmid} detected but ncbi returned no data") - nil - end - end - - def extract_data(xml) - { - article_title: xml.xpath('//ArticleTitle').text, - journal_title: xml.xpath('//Journal/Title').text, - journal_volume: xml.xpath('//Journal/JournalIssue/Volume').text, - journal_year: xml.xpath('//Journal/JournalIssue/PubDate/Year').text, - pages: xml.xpath('//Article/Pagination').text, - status: xml.xpath('//PublicationStatus').text - } - end - - def url(pmid) - "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=#{pmid}&retmode=xml" - end - - def fetch(pmid) - resp = HTTP.headers(accept: 'application/xml').get(url(pmid)) - - if resp.status == 200 - Nokogiri::XML(resp.to_s) - else - Rails.logger.debug("Fact lookup error. PMID #{pmid} detected but ncbi an error status") - Rails.logger.debug("URL: #{url(pmid)}") - 'Error' - end - end -end diff --git a/app/views/fact/doi.html.erb b/app/views/fact/doi.html.erb deleted file mode 100644 index 78baa8ca..00000000 --- a/app/views/fact/doi.html.erb +++ /dev/null @@ -1,31 +0,0 @@ -<% return unless @json.present? %> - -
- <% unless params[:slim].present? %> -
- <%= @json[:title] %>
-
- <% end %> - -
- In: <%= @json[:journal_name] %>, <%= @json[:year] %> - - -
-
diff --git a/app/views/fact/isbn.html.erb b/app/views/fact/isbn.html.erb deleted file mode 100644 index 92e0f69b..00000000 --- a/app/views/fact/isbn.html.erb +++ /dev/null @@ -1,21 +0,0 @@ -<% return unless @json.present? %> - -
-
- <%= link_to @json[:title], @json[:openurl] %> -
- -
- -
- - -
diff --git a/app/views/fact/issn.html.erb b/app/views/fact/issn.html.erb deleted file mode 100644 index 01e608dc..00000000 --- a/app/views/fact/issn.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -<% return unless @json.present? %> - -
-
- <%= link_to @json[:title], @json[:openurl] %> -
- -
-

Publisher: <%= @json[:publisher] %>

-
- - -
diff --git a/app/views/fact/pmid.html.erb b/app/views/fact/pmid.html.erb deleted file mode 100644 index 441043d4..00000000 --- a/app/views/fact/pmid.html.erb +++ /dev/null @@ -1,21 +0,0 @@ -<% return unless @json.present? %> - -
-
- <%= @json[:article_title] %> -
- -
- -
- - -
diff --git a/app/views/layouts/_skip_links.html.erb b/app/views/layouts/_skip_links.html.erb index d3705d8a..1d4d75fa 100644 --- a/app/views/layouts/_skip_links.html.erb +++ b/app/views/layouts/_skip_links.html.erb @@ -1,7 +1,7 @@ <% if controller.controller_name == 'record' && controller.action_name == 'view' %>