diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb index 2019bce38..8a84255e5 100644 --- a/app/controllers/protocols_controller.rb +++ b/app/controllers/protocols_controller.rb @@ -835,7 +835,7 @@ class ProtocolsController < ApplicationController temp_files_ids << temp_file.id end end - @job = Protocols::DocxImportJob.perform_later(temp_files_ids, current_user.id, current_team.id) + @job = Protocols::DocxImportJob.perform_later(temp_files_ids, user_id: current_user.id, team_id: current_team.id) render json: { job_id: @job.job_id } end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 5388244d5..dafa45d00 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -184,7 +184,7 @@ class ReportsController < ApplicationController log_activity(:generate_docx_report) ensure_report_template! - Reports::DocxJob.perform_later(@report.id, current_user.id, root_url) + Reports::DocxJob.perform_later(@report.id, user_id: current_user.id, root_url: root_url) render json: { message: I18n.t('projects.reports.index.generation.accepted_message') } @@ -400,7 +400,7 @@ class ReportsController < ApplicationController log_activity(:generate_pdf_report) ensure_report_template! - Reports::PdfJob.perform_later(@report.id, current_user.id) + Reports::PdfJob.perform_later(@report.id, user_id: current_user.id) end def ensure_report_template! diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 67ccc98fb..15ed92752 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -349,7 +349,7 @@ class RepositoriesController < ApplicationController repositories = Repository.viewable_by_user(current_user, current_team).where(id: params[:repository_ids]) if repositories.present? && current_user.has_available_exports? current_user.increase_daily_exports_counter! - RepositoriesExportJob.perform_later(repositories.pluck(:id), current_user.id, current_team) + RepositoriesExportJob.perform_later(repositories.pluck(:id), user_id: current_user.id, team_id: current_team.id) render json: { message: t('zip_export.export_request_success') } else render json: { message: t('zip_export.export_error') }, status: :unprocessable_entity diff --git a/app/jobs/concerns/failed_deivery_notifiable_job.rb b/app/jobs/concerns/failed_deivery_notifiable_job.rb new file mode 100644 index 000000000..07d12d487 --- /dev/null +++ b/app/jobs/concerns/failed_deivery_notifiable_job.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module FailedDeliveryNotifiableJobJob + extend ActiveSupport::Concern + + included do + before_enqueue do |job| + unless job.arguments.last.is_a?(Hash) && job.arguments.last[:user_id].present? + raise ArgumentError, 'required :user_id argument is missing! Needed for user notification in case of failure.' + end + end + + rescue_from StandardError do |e| + logger.error e.message + logger.error e.backtrace.join("\n") + create_failed_notification! + end + end + + private + + def create_failed_notification! + @user = User.find_by(id: arguments.last[:user_id]) + return if @user.blank? + + notification = Notification.create!( + type_of: :deliver_error, + title: failed_notification_title, + message: failed_notification_message + ) + notification.create_user_notification(@user) + end + + def failed_notification_title + I18n.t('activejob.failure_notifiable_job.general_notification_title') + end + + def failed_notification_message + I18n.backend.date_format = @user.settings[:date_format] + timestamp = I18n.l(enqueued_at.in_time_zone(@user.time_zone), format: :full) + I18n.t('activejob.failure_notifiable_job.general_notification_message', request_timestamp: timestamp) + ensure + I18n.backend.date_format = nil + end +end diff --git a/app/jobs/protocols/docx_import_job.rb b/app/jobs/protocols/docx_import_job.rb index 0b1ae3003..04e6d2084 100644 --- a/app/jobs/protocols/docx_import_job.rb +++ b/app/jobs/protocols/docx_import_job.rb @@ -3,10 +3,11 @@ module Protocols class DocxImportJob < ApplicationJob include RenamingUtil + include FailedDeliveryNotifiableJob class RequestFailureException < StandardError; end - def perform(temp_files_ids, user_id, team_id) + def perform(temp_files_ids, user_id:, team_id:) @user = User.find(user_id) @team = @user.teams.find(team_id) @tmp_files = TempFile.where(id: temp_files_ids) @@ -30,10 +31,7 @@ module Protocols } ) - unless response.success? - create_failed_notification! - raise RequestFailureException, "#{response.code}: #{response.message}" - end + raise RequestFailureException, "#{response.code}: #{response.message}" unless response.success? ActiveRecord::Base.transaction do @protocol = @team.protocols.new( @@ -50,9 +48,6 @@ module Protocols @protocol.save! create_steps!(response['steps']) if response['steps'].present? create_notification! - rescue ActiveRecord::RecordInvalid => e - create_failed_notification! - Rails.logger.error(e.message) end end @@ -153,13 +148,14 @@ module Protocols UserNotification.create!(notification: notification, user: @user) end - def create_failed_notification! - notification = Notification.create!( - type_of: :deliver_error, - title: I18n.t('protocols.import_export.import_protocol_notification_error.title') - ) + # Overrides method from FailedDeliveryNotifiableJob concern + def failed_notification_title + I18n.t('protocols.import_export.import_protocol_notification_error.title') + end - UserNotification.create!(notification: notification, user: @user) + # Overrides method from FailedDeliveryNotifiableJob concern + def failed_notification_message + I18n.t('protocols.import_export.import_protocol_notification_error.message') end end end diff --git a/app/jobs/reports/docx_job.rb b/app/jobs/reports/docx_job.rb index 53fa27ba3..6f9cc6d92 100644 --- a/app/jobs/reports/docx_job.rb +++ b/app/jobs/reports/docx_job.rb @@ -4,36 +4,11 @@ module Reports class DocxJob < ApplicationJob extend InputSanitizeHelper include InputSanitizeHelper + include FailedDeliveryNotifiableJob queue_as :reports - discard_on StandardError do |job, error| - report = Report.find_by(id: job.arguments.first) - next unless report - - ActiveRecord::Base.no_touching do - report.docx_error! - end - report_path = - if report.docx_file.attached? - Rails.application.routes.url_helpers - .reports_path(team: report.team.id, preview_report_id: report.id, preview_type: :docx) - else - Rails.application.routes.url_helpers.reports_path(team: report.team.id) - end - user = User.find(job.arguments.second) - notification = Notification.create( - type_of: :deliver_error, - title: I18n.t('projects.reports.index.generation.error_docx_notification_title'), - message: I18n.t('projects.reports.index.generation.error_notification_message', - report_link: "#{escape_input(report.name)}", - team_name: escape_input(report.team.name)) - ) - notification.create_user_notification(user) - Rails.logger.error("Couldn't generate DOCX for Report with id: #{report.id}. Error:\n #{error}") - end - - def perform(report_id, user_id, root_url) + def perform(report_id, user_id:, root_url:) report = Report.find(report_id) user = User.find(user_id) file = Tempfile.new(['report', '.docx']) @@ -61,6 +36,39 @@ module Reports file.close file.unlink end + rescue StandardError => e + raise e if report.blank? + + ActiveRecord::Base.no_touching do + report.docx_error! + end + Rails.logger.error("Couldn't generate DOCX for Report with id: #{report.id}. Error:\n #{e.message}") + raise e + end + + private + + # Overrides method from FailedDeliveryNotifiableJob concern + def failed_notification_title + I18n.t('projects.reports.index.generation.error_docx_notification_title') + end + + # Overrides method from FailedDeliveryNotifiableJob concern + def failed_notification_message + report = Report.find_by(id: arguments.first) + return '' if report.blank? + + report_path = + if report.docx_file.attached? + Rails.application.routes.url_helpers + .reports_path(team: report.team.id, preview_report_id: report.id, preview_type: :docx) + else + Rails.application.routes.url_helpers.reports_path(team: report.team.id) + end + + I18n.t('projects.reports.index.generation.error_notification_message', + report_link: "#{escape_input(report.name)}", + team_name: escape_input(report.team.name)) end end end diff --git a/app/jobs/reports/pdf_job.rb b/app/jobs/reports/pdf_job.rb index 2fb9a95bc..2bce30b56 100644 --- a/app/jobs/reports/pdf_job.rb +++ b/app/jobs/reports/pdf_job.rb @@ -6,40 +6,15 @@ module Reports include InputSanitizeHelper include ReportsHelper include Canaid::Helpers::PermissionsHelper + include FailedDeliveryNotifiableJob PDFUNITE_ENCRYPTED_PDF_ERROR_STRING = 'Unimplemented Feature: Could not merge encrypted files' queue_as :reports - discard_on StandardError do |job, error| - report = Report.find_by(id: job.arguments.first) - next unless report - - ActiveRecord::Base.no_touching do - report.pdf_error! - end - report_path = - if report.pdf_file.attached? - Rails.application.routes.url_helpers - .reports_path(team: report.team.id, preview_report_id: report.id, preview_type: :pdf) - else - Rails.application.routes.url_helpers.reports_path(team: report.team.id) - end - user = User.find(job.arguments.second) - notification = Notification.create( - type_of: :deliver_error, - title: I18n.t('projects.reports.index.generation.error_pdf_notification_title'), - message: I18n.t('projects.reports.index.generation.error_notification_message', - report_link: "#{escape_input(report.name)}", - team_name: escape_input(report.team.name)) - ) - notification.create_user_notification(user) - Rails.logger.error("Couldn't generate PDF for Report with id: #{report.id}. Error:\n #{error}") - end - PREVIEW_EXTENSIONS = %w(docx pdf).freeze - def perform(report_id, user_id) + def perform(report_id, user_id:) report = Report.find(report_id) user = User.find(user_id) file = Tempfile.new(['report', '.pdf'], binmode: true) @@ -97,6 +72,14 @@ module Reports I18n.backend.date_format = nil file.close(true) end + rescue StandardError => e + raise e if report.blank? + + ActiveRecord::Base.no_touching do + report.pdf_error! + end + Rails.logger.error("Couldn't generate PDF for Report with id: #{report.id}. Error:\n #{e.message}") + raise e end private @@ -195,5 +178,27 @@ module Reports 'scinote_logo.svg' end + # Overrides method from FailedDeliveryNotifiableJob concern + def failed_notification_title + I18n.t('projects.reports.index.generation.error_pdf_notification_title') + end + + # Overrides method from FailedDeliveryNotifiableJob concern + def failed_notification_message + report = Report.find_by(id: arguments.first) + return '' if report.blank? + + report_path = + if report.pdf_file.attached? + Rails.application.routes.url_helpers + .reports_path(team: report.team.id, preview_report_id: report.id, preview_type: :pdf) + else + Rails.application.routes.url_helpers.reports_path(team: report.team.id) + end + + I18n.t('projects.reports.index.generation.error_notification_message', + report_link: "#{escape_input(report.name)}", + team_name: escape_input(report.team.name)) + end end end diff --git a/app/jobs/repositories_export_job.rb b/app/jobs/repositories_export_job.rb index 288506a82..bd5c46e37 100644 --- a/app/jobs/repositories_export_job.rb +++ b/app/jobs/repositories_export_job.rb @@ -2,10 +2,11 @@ class RepositoriesExportJob < ApplicationJob include StringUtility + include FailedDeliveryNotifiableJob - def perform(repository_ids, user_id, team) + def perform(repository_ids, user_id:, team_id:) @user = User.find(user_id) - @team = team + @team = Team.find(team_id) @repositories = Repository.viewable_by_user(@user, @team).where(id: repository_ids).order(:id) zip_input_dir = FileUtils.mkdir_p(Rails.root.join("tmp/temp_zip_#{Time.now.to_i}")).first zip_dir = FileUtils.mkdir_p(Rails.root.join('tmp/zip-ready')).first @@ -95,4 +96,9 @@ class RepositoriesExportJob < ApplicationJob ) UserNotification.create!(notification: notification, user: @user) end + + # Overrides method from FailedDeliveryNotifiableJob concern + def failed_notification_title + I18n.t('repositories.index.export.notification.error.title') + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 41673a71a..11d1a8408 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -253,6 +253,11 @@ en: full_name: "Full name" initials: "Initials" avatar: "Avatar" + + activejob: + failure_notifiable_job: + general_notification_title: "Your export failed. Please contact support." + general_notification_message: "Request timestamp: %{request_timestamp}" head: title: "SciNote | %{title}" @@ -1892,6 +1897,10 @@ en: all_teams: "All teams (current & new)" all_teams_tooltip: "This will disable individual team settings" success_message: "Selected sharing options for the Inventory %{inventory_name} have been saved." + export: + notification: + error: + title: "Your Inventories export failed. Please contact support." show: name: "Name" archived_inventory_items: "%{repository_name} archived items" @@ -2773,7 +2782,7 @@ en: body_html: 'The protocol has been successfully imported. You can access the draft of protocol template here.' failed: title: "Import failed" - body_html: "We're sorry, but the file import has failed. Please ensure you have a stable internet connection and try again. If the problem persists, please contact support for further assistance." + body_html: "We're sorry but the file import process has encountered an error. Please make another attempt, and if the issue persists, please contact support for further assistance." import: "Import" cancel: "Cancel" close: "Close" @@ -2872,7 +2881,8 @@ en: title: "The import process has been successfully completed. You can download original file here: %{link}" message: "Protocol template:" import_protocol_notification_error: - title: "We're sorry, but the file import has failed. Please ensure you have a stable internet connection and try again. If the problem persists, please contact support for further assistance." + title: "Import failed" + message: "We're sorry but the file import process has encountered an error. Please make another attempt, and if the issue persists, please contact support for further assistance." export: export_results: title: "Export results"