diff --git a/lib/ontologies_linked_data.rb b/lib/ontologies_linked_data.rb index 678e0e89..75b7c8bc 100644 --- a/lib/ontologies_linked_data.rb +++ b/lib/ontologies_linked_data.rb @@ -17,6 +17,7 @@ require "ontologies_linked_data/utils/file" require "ontologies_linked_data/utils/triples" require "ontologies_linked_data/utils/notifications" +require "ontologies_linked_data/utils/notifier" require "ontologies_linked_data/utils/ontology_csv_writer" require "ontologies_linked_data/utils/multi_logger" require "ontologies_linked_data/parser/parser" diff --git a/lib/ontologies_linked_data/config/config.rb b/lib/ontologies_linked_data/config/config.rb index c30c70d2..e771f670 100644 --- a/lib/ontologies_linked_data/config/config.rb +++ b/lib/ontologies_linked_data/config/config.rb @@ -37,9 +37,12 @@ def config(&block) # @settings.redis_port ||= 6379 # ### + + @settings.ui_name ||= 'Bioportal' @settings.ui_host ||= 'bioportal.bioontology.org' - @settings.replace_url_prefix ||= false - @settings.id_url_prefix ||= 'http://data.bioontology.org/' + @settings.replace_url_prefix ||= false + @settings.id_url_prefix ||= "http://data.bioontology.org/" + @settings.queries_debug ||= false @settings.enable_monitoring ||= false @settings.cube_host ||= 'localhost' diff --git a/lib/ontologies_linked_data/utils/notifications.rb b/lib/ontologies_linked_data/utils/notifications.rb index 340c0d5d..beb6cf4b 100644 --- a/lib/ontologies_linked_data/utils/notifications.rb +++ b/lib/ontologies_linked_data/utils/notifications.rb @@ -1,206 +1,144 @@ require 'cgi' require 'pony' -module LinkedData::Utils - class Notifications - - def self.notify(options = {}) - return unless LinkedData.settings.enable_notifications - - headers = { 'Content-Type' => 'text/html' } - sender = options[:sender] || LinkedData.settings.email_sender - recipients = options[:recipients] - raise ArgumentError, "Recipient needs to be provided in options[:recipients]" if !recipients || recipients.empty? - - # By default we override all recipients to avoid - # sending emails from testing environments. - # Set `email_disable_override` in production - # to send to the actual user. - unless LinkedData.settings.email_disable_override - headers['Overridden-Sender'] = recipients - recipients = LinkedData.settings.email_override +module LinkedData + module Utils + class Notifications + def self.new_note(note) + note.bring_remaining + note.creator.bring(:username) if note.creator.bring?(:username) + note.relatedOntology.each { |o| o.bring(:name) if o.bring?(:name); o.bring(:subscriptions) if o.bring?(:subscriptions) } + ontologies = note.relatedOntology.map { |o| o.name }.join(", ") + # Fix the note URL when using replace_url_prefix (in another VM than NCBO) + if LinkedData.settings.replace_url_prefix + note_id = CGI.escape(note.id.to_s.gsub(LinkedData.settings.id_url_prefix, LinkedData.settings.rest_url_prefix)) + else + note_id = CGI.escape(note.id.to_s) + end + note_url = "http://#{LinkedData.settings.ui_host}/notes/#{note_id}" + subject = "[#{LinkedData.settings.ui_name} Notes] [#{ontologies}] #{note.subject}" + body = NEW_NOTE.gsub("%username%", note.creator.username) + .gsub("%ontologies%", ontologies) + .gsub("%note_subject%", note.subject || "") + .gsub("%note_body%", note.body || "") + .gsub("%note_url%", note_url) + .gsub('%ui_name%', LinkedData.settings.ui_name) + + note.relatedOntology.each do |ont| + Notifier.notify_subscribed_separately subject, body, ont, 'NOTES' + end end - Pony.mail({ - to: recipients, - from: sender, - subject: options[:subject], - body: options[:body], - headers: headers, - via: :smtp, - enable_starttls_auto: LinkedData.settings.enable_starttls_auto, - via_options: mail_options - }) - end - - def self.new_note(note) - note.bring_remaining - note.creator.bring(:username) if note.creator.bring?(:username) - note.relatedOntology.each {|o| o.bring(:name) if o.bring?(:name); o.bring(:subscriptions) if o.bring?(:subscriptions)} - ontologies = note.relatedOntology.map {|o| o.name}.join(", ") - subject = "[BioPortal Notes] [#{ontologies}] #{note.subject}" - body = NEW_NOTE.gsub("%username%", note.creator.username) - .gsub("%ontologies%", ontologies) - .gsub("%note_url%", LinkedData::Hypermedia.generate_links(note)["ui"]) - .gsub("%note_subject%", note.subject || "") - .gsub("%note_body%", note.body || "") - - options = { - ontologies: note.relatedOntology, - notification_type: "NOTES", - subject: subject, - body: body - } - send_ontology_notifications(options) - end + def self.submission_processed(submission) + submission.bring_remaining + ontology = submission.ontology + ontology.bring(:name, :acronym) + result = submission.ready? ? 'Success' : 'Failure' + status = LinkedData::Models::SubmissionStatus.readable_statuses(submission.submissionStatus) + + subject = "[#{LinkedData.settings.ui_name}] #{ontology.name} Parsing #{result}" + body = SUBMISSION_PROCESSED.gsub('%ontology_name%', ontology.name) + .gsub('%ontology_acronym%', ontology.acronym) + .gsub('%statuses%', status.join('
')) + .gsub('%admin_email%', LinkedData.settings.email_sender) + .gsub('%ontology_location%', LinkedData::Hypermedia.generate_links(ontology)['ui']) + .gsub('%ui_name%', LinkedData.settings.ui_name) + + Notifier.notify_subscribed_separately subject, body, ontology, 'PROCESSING' + Notifier.notify_mails_grouped subject, body, Notifier.support_mails + Notifier.admin_mails(ontology) + end - def self.submission_processed(submission) - submission.bring_remaining - ontology = submission.ontology - ontology.bring(:name, :acronym) - result = submission.ready? ? "Success" : "Failure" - status = LinkedData::Models::SubmissionStatus.readable_statuses(submission.submissionStatus) - - subject = "[BioPortal] #{ontology.name} Parsing #{result}" - body = SUBMISSION_PROCESSED.gsub("%ontology_name%", ontology.name) - .gsub("%ontology_acronym%", ontology.acronym) - .gsub("%statuses%", status.join("
")) - .gsub("%admin_email%", LinkedData.settings.email_sender) - .gsub("%ontology_location%", LinkedData::Hypermedia.generate_links(ontology)["ui"]) - - options = { - ontologies: ontology, - notification_type: "PROCESSING", - subject: subject, - body: body - } - send_ontology_notifications(options) - end + def self.remote_ontology_pull(submission) + submission.bring_remaining + ontology = submission.ontology + ontology.bring(:name, :acronym, :administeredBy) + + subject = "[#{LinkedData.settings.ui_name}] Load from URL failure for #{ontology.name}" + body = REMOTE_PULL_FAILURE.gsub('%ont_pull_location%', submission.pullLocation.to_s) + .gsub('%ont_name%', ontology.name) + .gsub('%ont_acronym%', ontology.acronym) + .gsub('%ontology_location%', LinkedData::Hypermedia.generate_links(ontology)['ui']) + .gsub('%support_mail%', Notifier.support_mails.first || '') + .gsub('%ui_name%', LinkedData.settings.ui_name) + recipients = [] + ontology.administeredBy.each do |user| + user.bring(:email) if user.bring?(:email) + recipients << user.email + end + if !LinkedData.settings.admin_emails.nil? && LinkedData.settings.admin_emails.kind_of?(Array) + LinkedData.settings.admin_emails.each do |admin_email| + recipients << admin_email + end + end - def self.remote_ontology_pull(submission) - submission.bring_remaining - ontology = submission.ontology - ontology.bring(:name, :acronym, :administeredBy) - - subject = "[BioPortal] Load from URL failure for #{ontology.name}" - body = REMOTE_PULL_FAILURE.gsub("%ont_pull_location%", submission.pullLocation.to_s) - .gsub("%ont_name%", ontology.name) - .gsub("%ont_acronym%", ontology.acronym) - .gsub("%ontology_location%", LinkedData::Hypermedia.generate_links(ontology)["ui"]) - recipients = [] - ontology.administeredBy.each do |user| - user.bring(:email) if user.bring?(:email) - recipients << user.email + Notifier.notify_mails_grouped subject, body, [Notifier.admin_mails(ontology) + Notifier.support_mails] end - options = { - subject: subject, - body: body, - recipients: recipients - } - notify(options) - end + def self.new_user(user) + user.bring_remaining - def self.reset_password(user, token) - subject = "[BioPortal] User #{user.username} password reset" - password_url = "https://#{LinkedData.settings.ui_host}/reset_password?tk=#{token}&em=#{CGI.escape(user.email)}&un=#{CGI.escape(user.username)}" - - body = <<~HTML - Someone has requested a password reset for user #{user.username}. If this was - you, please click on the link below to reset your password. Otherwise, please - ignore this email.

+ subject = "[#{LinkedData.settings.ui_name}] New User: #{user.username}" + body = NEW_USER_CREATED.gsub('%username%', user.username.to_s) + .gsub('%email%', user.email.to_s) + .gsub('%site_url%', LinkedData.settings.ui_host) + .gsub('%ui_name%', LinkedData.settings.ui_name) + recipients = LinkedData.settings.admin_emails - #{password_url}

+ Notifier.notify_support_grouped subject, body + end - Thanks,
- BioPortal Team - HTML - - options = { - subject: subject, - body: body, - recipients: user.email - } - notify(options) - end + def self.new_ontology(ont) + ont.bring_remaining - def self.obofoundry_sync(missing_onts, obsolete_onts) - body = "" + subject = "[#{LinkedData.settings.ui_name}] New Ontology: #{ont.acronym}" + body = NEW_ONTOLOGY_CREATED.gsub('%acronym%', ont.acronym) + .gsub('%name%', ont.name.to_s) + .gsub('%addedby%', ont.administeredBy[0].to_s) + .gsub('%site_url%', LinkedData.settings.ui_host) + .gsub('%ont_url%', LinkedData::Hypermedia.generate_links(ont)['ui']) + .gsub('%ui_name%', LinkedData.settings.ui_name) + recipients = LinkedData.settings.admin_emails - if missing_onts.size > 0 - body << "The following OBO Library ontologies are missing from BioPortal:

" - missing_onts.each do |ont| - body << "#{ont['id']} (#{ont['title']})

" - end + Notifier.notify_support_grouped subject, body end - if obsolete_onts.size > 0 - body << "The following OBO Library ontologies have been deprecated:

" - obsolete_onts.each do |ont| - body << "#{ont['id']} (#{ont['title']})

" - end - end + def self.reset_password(user, token) + ui_name = LinkedData.settings.ui_name + subject = "[#{ui_name}] User #{user.username} password reset" + password_url = "https://#{LinkedData.settings.ui_host}/reset_password?tk=#{token}&em=#{CGI.escape(user.email)}&un=#{CGI.escape(user.username)}" - if body.empty? - body << "BioPortal and the OBO Foundry are in sync.

" + body = REST_PASSWORD.gsub('%ui_name%', ui_name) + .gsub('%username%', user.username.to_s) + .gsub('%password_url%', password_url.to_s) + + Notifier.notify_mails_separately subject, body, [user.email] end - options = { - subject: "[BioPortal] OBOFoundry synchronization report", - body: body, - recipients: LinkedData.settings.email_sender - } - notify(options) - end + def self.obofoundry_sync(missing_onts, obsolete_onts) + body = '' + ui_name = LinkedData.settings.ui_name + if missing_onts.size > 0 + body << "The following OBO Library ontologies are missing from #{ui_name}:

" + missing_onts.each do |ont| + body << "#{ont['id']} (#{ont['title']})

" + end + end - private - - ## - # This method takes a list of ontologies and a notification type, - # then looks up all the users who subscribe to that ontology/type pair - # and sends them an email with the given subject and body. - def self.send_ontology_notifications(options = {}) - ontologies = options[:ontologies] - ontologies = ontologies.is_a?(Array) ? ontologies : [ontologies] - notification_type = options[:notification_type] - subject = options[:subject] - body = options[:body] - emails = [] - ontologies.each {|o| o.bring(:subscriptions) if o.bring?(:subscriptions)} - ontologies.each do |ont| - ont.subscriptions.each do |subscription| - subscription.bring(:notification_type) if subscription.bring?(:notification_type) - subscription.notification_type.bring(:type) if subscription.notification_type.bring?(:notification_type) - next unless subscription.notification_type.type.eql?(notification_type.to_s.upcase) || subscription.notification_type.type.eql?("ALL") - subscription.bring(:user) if subscription.bring?(:user) - subscription.user.each do |user| - user.bring(:email) if user.bring?(:email) - emails << notify(recipients: user.email, subject: subject, body: body) + if obsolete_onts.size > 0 + body << 'The following OBO Library ontologies have been deprecated:

' + obsolete_onts.each do |ont| + body << "#{ont['id']} (#{ont['title']})

" end end - end - emails - end - def self.mail_options - options = { - address: LinkedData.settings.smtp_host, - port: LinkedData.settings.smtp_port, - domain: LinkedData.settings.smtp_domain # the HELO domain provided by the client to the server - } - - if LinkedData.settings.smtp_auth_type && LinkedData.settings.smtp_auth_type != :none - options.merge({ - user_name: LinkedData.settings.smtp_user, - password: LinkedData.settings.smtp_password, - authentication: LinkedData.settings.smtp_auth_type - }) - end + if body.empty? + body << "#{ui_name} and the OBO Foundry are in sync.

" + end - return options - end + Notifier.notify_mails_separately subject, body, [LinkedData.settings.email_sender] + end -NEW_NOTE = <%username%.

----------------------------------------------------------------------------------
@@ -209,37 +147,71 @@ def self.mail_options %note_body%
----------------------------------------------------------------------------------

-You can respond by visiting: NCBO BioPortal.

+You can respond by visiting: %ui_name%.

EOS -SUBMISSION_PROCESSED = <
%statuses%

Please contact %admin_email% if you have questions.

-The ontology can be browsed in BioPortal. +The ontology can be browsed in %ui_name%.

Thank you,
-The BioPortal Team +The %ui_name% Team EOS -REMOTE_PULL_FAILURE = <
Please verify the URL you provided for daily loading of your ontology:
    -
  1. Make sure you are signed in to BioPortal.
  2. -
  3. Navigate to your ontology summary page: %ontology_location%.
  4. +
  5. Make sure you are signed in to %ui_name%.
  6. +
  7. Navigate to your ontology summary page: %ontology_location%.
  8. Click the "Edit submission information" link.
  9. In the Location row, verify that you entered a valid URL for daily loading of your ontology in the URL text area.
-If you need further assistance, please contact us via the BioPortal support mailing list. +If you need further assistance, please contact us via the %ui_name% support mailing list.

Thank you,
-The BioPortal Team +The %ui_name% Team +EOS + + NEW_USER_CREATED = < +Username: %username% +
+Email: %email% +

+The %ui_name% Team EOS + NEW_ONTOLOGY_CREATED = < +Acronym: %acronym% +
+Name: %name% +
+At %ont_url% +

+The %ui_name% Team +EOS + + REST_PASSWORD = <<~HTML + Someone has requested a password reset for user %username% . If this was + you, please click on the link below to reset your password. Otherwise, please + ignore this email.

+ + %password_url%

+ + Thanks,
+ %ui_name% Team +HTML + + end end -end \ No newline at end of file +end diff --git a/lib/ontologies_linked_data/utils/notifier.rb b/lib/ontologies_linked_data/utils/notifier.rb new file mode 100644 index 00000000..70f109e0 --- /dev/null +++ b/lib/ontologies_linked_data/utils/notifier.rb @@ -0,0 +1,125 @@ +module LinkedData + module Utils + class Notifier + def self.notify(options = {}) + return unless LinkedData.settings.enable_notifications + + headers = { 'Content-Type' => 'text/html' } + sender = options[:sender] || LinkedData.settings.email_sender + recipients = Array(options[:recipients]).uniq + raise ArgumentError, 'Recipient needs to be provided in options[:recipients]' if !recipients || recipients.empty? + + # By default we override all recipients to avoid + # sending emails from testing environments. + # Set `email_disable_override` in production + # to send to the actual user. + unless LinkedData.settings.email_disable_override + headers['Overridden-Sender'] = recipients + recipients = LinkedData.settings.email_override + end + + Pony.mail({ + to: recipients, + from: sender, + subject: options[:subject], + body: options[:body], + headers: headers, + via: :smtp, + enable_starttls_auto: LinkedData.settings.enable_starttls_auto, + via_options: mail_options + }) + end + + def self.notify_support_grouped(subject, body) + notify_mails_grouped subject, body, support_mails + end + + def self.notify_subscribed_separately(subject, body, ontology, notification_type) + mails = subscribed_users_mails(ontology, notification_type) + notify_mails_separately subject, body, mails + end + + def self.notify_administrators_grouped(subject, body, ontology) + notify_support_grouped subject, body, admin_mails(ontology) + end + + def self.notify_mails_separately(subject, body, mails) + mails.each do |mail| + notify_mails_grouped subject, body, mail + end + end + + def self.notify_mails_grouped(subject, body, mail) + options = { + subject: subject, + body: body, + recipients: mail + } + notify(options) + end + + def self.notify_support(subject, body) + notify_mails_grouped subject, body, support_mails + end + + def self.admin_mails(ontology) + ontology.bring :administeredBy if ontology.bring? :administeredBy + recipients = [] + ontology.administeredBy.each do |user| + user.bring(:email) if user.bring?(:email) + recipients << user.email + end + recipients + end + + def self.support_mails + + if !LinkedData.settings.admin_emails.nil? && + LinkedData.settings.admin_emails.kind_of?(Array) + return LinkedData.settings.admin_emails + end + [] + end + + def self.subscribed_users_mails(ontology, notification_type) + emails = [] + ontology.bring(:subscriptions) if ontology.bring?(:subscriptions) + ontology.subscriptions.each do |subscription| + + subscription.bring(:notification_type) if subscription.bring?(:notification_type) + subscription.notification_type.bring(:type) if subscription.notification_type.bring?(:notification_type) + + unless subscription.notification_type.type.eql?(notification_type.to_s.upcase) || + subscription.notification_type.type.eql?('ALL') + next + end + + subscription.bring(:user) if subscription.bring?(:user) + subscription.user.each do |user| + user.bring(:email) if user.bring?(:email) + emails << user.email + end + end + emails + end + + def self.mail_options + options = { + address: LinkedData.settings.smtp_host, + port: LinkedData.settings.smtp_port, + domain: LinkedData.settings.smtp_domain # the HELO domain provided by the client to the server + } + + if LinkedData.settings.smtp_auth_type && LinkedData.settings.smtp_auth_type != :none + options.merge!({ + user_name: LinkedData.settings.smtp_user, + password: LinkedData.settings.smtp_password, + authentication: LinkedData.settings.smtp_auth_type + }) + end + + options + end + end + end +end diff --git a/test/util/test_notifications.rb b/test/util/test_notifications.rb index 8508dc1e..c8bc7017 100644 --- a/test/util/test_notifications.rb +++ b/test/util/test_notifications.rb @@ -8,8 +8,14 @@ class TestNotifications < LinkedData::TestCase def self.before_suite @@notifications_enabled = LinkedData.settings.enable_notifications @@disable_override = LinkedData.settings.email_disable_override + @@old_support_mails = LinkedData.settings.admin_emails + if @@old_support_mails.nil? || @@old_support_mails.empty? + LinkedData.settings.admin_emails = ["ontoportal-support@mail.com"] + end LinkedData.settings.email_disable_override = true LinkedData.settings.enable_notifications = true + @@ui_name = LinkedData.settings.ui_name + @@support_mails = LinkedData.settings.admin_emails @@ont = LinkedData::SampleData::Ontology.create_ontologies_and_submissions(ont_count: 1, submission_count: 1)[2].first @@ont.bring_remaining @@user = @@ont.administeredBy.first @@ -22,6 +28,7 @@ def self.before_suite def self.after_suite LinkedData.settings.enable_notifications = @@notifications_enabled LinkedData.settings.email_disable_override = @@disable_override + LinkedData.settings.admin_emails = @@old_support_mails @@ont.delete if defined?(@@ont) @@subscription.delete if defined?(@@subscription) @@user.delete if defined?(@@user) @@ -45,16 +52,16 @@ def test_send_notification # Email recipient address will be overridden LinkedData.settings.email_disable_override = false - LinkedData::Utils::Notifications.notify(recipients: recipients) + LinkedData::Utils::Notifier.notify(recipients: recipients) assert_equal [LinkedData.settings.email_override], last_email_sent.to # Disable override LinkedData.settings.email_disable_override = true - LinkedData::Utils::Notifications.notify({ - recipients: recipients, - subject: subject, - body: body - }) + LinkedData::Utils::Notifier.notify({ + recipients: recipients, + subject: subject, + body: body + }) assert_equal recipients, last_email_sent.to assert_equal [LinkedData.settings.email_sender], last_email_sent.from assert_equal last_email_sent.body.raw_source, body @@ -71,8 +78,7 @@ def test_new_note_notification note.body = body note.relatedOntology = [@@ont] note.save - - assert last_email_sent.subject.include?("[BioPortal Notes]") + assert last_email_sent.subject.include?("[#{@@ui_name} Notes]") assert_equal [@@user.email], last_email_sent.to ensure note.delete if note @@ -81,14 +87,27 @@ def test_new_note_notification def test_processing_complete_notification begin - options = {ont_count: 1, submission_count: 1, acronym: "NOTIFY"} + options = { ont_count: 1, submission_count: 1, acronym: "NOTIFY" } ont = LinkedData::SampleData::Ontology.create_ontologies_and_submissions(options)[2].first subscription = _subscription(ont) @@user.subscription = @@user.subscription.dup << subscription @@user.save ont.latest_submission(status: :any).process_submission(Logger.new(TestLogFile.new)) - assert last_email_sent.subject.include?("Parsing Success") - assert_equal [@@user.email], last_email_sent.to + subscription.bring :user + admin_mails = LinkedData::Utils::Notifier.admin_mails(ont) + mail_sent_count = subscription.user.size + 1 + mails = all_emails.last(mail_sent_count) + assert_equal mail_sent_count, mails.size # subscribed users + support mail + + first_user = subscription.user.first + first_user.bring :email + assert mails.first.subject.include?("Parsing Success") + assert_equal [first_user.email], mails.first.to + + + assert mails.last.subject.include?("Parsing Success") + assert_equal (@@support_mails + admin_mails).uniq.sort, mails.last.to.sort + ensure ont.delete if ont subscription.delete if subscription @@ -117,8 +136,8 @@ def test_remote_ontology_pull_notification assert sub.valid?, sub.errors LinkedData::Utils::Notifications.remote_ontology_pull(sub) - assert last_email_sent.subject.include? "[BioPortal] Load from URL failure for #{ont.name}" - recipients = [] + assert last_email_sent.subject.include? "[#{@@ui_name}] Load from URL failure for #{ont.name}" + recipients = @@support_mails ont_admins.each do |user| recipients << user.email end @@ -129,4 +148,30 @@ def test_remote_ontology_pull_notification end end end + + def test_mail_options + options = LinkedData::Utils::Notifier.mail_options + expected_options = { + address: LinkedData.settings.smtp_host, + port: LinkedData.settings.smtp_port, + domain: LinkedData.settings.smtp_domain + } + assert_equal options, expected_options + + # testing SMTP authentification-based login + current_auth_type = LinkedData.settings.smtp_auth_type + LinkedData.settings.smtp_auth_type = :plain + options = LinkedData::Utils::Notifier.mail_options + expected_options = { + address: LinkedData.settings.smtp_host, + port: LinkedData.settings.smtp_port, + domain: LinkedData.settings.smtp_domain, + user_name: LinkedData.settings.smtp_user, + password: LinkedData.settings.smtp_password, + authentication: LinkedData.settings.smtp_auth_type + } + assert_equal options, expected_options + + LinkedData.settings.smtp_auth_type = current_auth_type + end end