diff --git a/Gemfile b/Gemfile
index e90ed1171..512c38b36 100644
--- a/Gemfile
+++ b/Gemfile
@@ -56,6 +56,7 @@ gem 'jbuilder' # JSON structures via a Builder-style DSL
gem 'logging', '~> 2.0.0'
gem 'nested_form_fields'
gem 'nokogiri', '~> 1.14.3' # HTML/XML parser
+gem 'noticed'
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
gem 'rgl' # Graph framework for project diagram calculations
gem 'roo', '~> 2.10.0' # Spreadsheet parser
diff --git a/Gemfile.lock b/Gemfile.lock
index 983c917d8..7d3d534ae 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -298,6 +298,8 @@ GEM
discard (1.2.1)
activerecord (>= 4.2, < 8)
docile (1.4.0)
+ domain_name (0.5.20190701)
+ unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.6.6)
railties (>= 5)
down (5.4.1)
@@ -321,6 +323,9 @@ GEM
faraday-net_http (3.0.2)
fastimage (2.2.7)
ffi (1.15.5)
+ ffi-compiler (1.0.1)
+ ffi (>= 1.0.0)
+ rake
figaro (1.2.0)
thor (>= 0.14.0, < 2)
fugit (1.8.1)
@@ -332,6 +337,14 @@ GEM
process-pipeline
hashdiff (1.0.1)
hashie (5.0.0)
+ http (5.1.1)
+ addressable (~> 2.8)
+ http-cookie (~> 1.0)
+ http-form_data (~> 2.2)
+ llhttp-ffi (~> 0.4.0)
+ http-cookie (1.0.5)
+ domain_name (~> 0.5)
+ http-form_data (2.3.0)
httparty (0.21.0)
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
@@ -381,6 +394,9 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
little-plugger (1.1.4)
+ llhttp-ffi (0.4.0)
+ ffi-compiler (~> 1.0)
+ rake (~> 13.0)
logging (2.0.0)
little-plugger (~> 1.1)
multi_json (~> 1.10)
@@ -428,6 +444,9 @@ GEM
racc (~> 1.4)
nokogiri (1.14.5-x86_64-linux)
racc (~> 1.4)
+ noticed (1.6.3)
+ http (>= 4.0.0)
+ rails (>= 5.2.0)
oauth2 (2.0.9)
faraday (>= 0.17.3, < 3.0)
jwt (>= 1.0, < 3.0)
@@ -654,6 +673,9 @@ GEM
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
underscore-rails (1.8.3)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.8.2)
unicode-display_width (2.4.2)
uniform_notifier (1.16.0)
version_gem (1.1.3)
@@ -740,6 +762,7 @@ DEPENDENCIES
nested_form_fields
newrelic_rpm
nokogiri (~> 1.14.3)
+ noticed
omniauth (~> 2.1)
omniauth-azure-activedirectory-v2
omniauth-linkedin-oauth2
diff --git a/app/controllers/user_notifications_controller.rb b/app/controllers/user_notifications_controller.rb
index 214ccc033..eb2c7e976 100644
--- a/app/controllers/user_notifications_controller.rb
+++ b/app/controllers/user_notifications_controller.rb
@@ -10,14 +10,12 @@ class UserNotificationsController < ApplicationController
next_page: notifications.next_page
}
- UserNotification.where(
- notification_id: notifications.except(:select).where.not(type_of: 2).select(:id)
- ).seen_by_user(current_user)
+ notifications.mark_as_read!
end
def unseen_counter
render json: {
- unseen: load_notifications.where('user_notifications.checked = ?', false).size
+ unseen: load_notifications.where(read_at: nil).size
}
end
@@ -25,7 +23,6 @@ class UserNotificationsController < ApplicationController
def load_notifications
current_user.notifications
- .select(:id, :type_of, :title, :message, :created_at, 'user_notifications.checked')
.order(created_at: :desc)
end
@@ -33,12 +30,12 @@ class UserNotificationsController < ApplicationController
notifications.map do |notification|
{
id: notification.id,
- type_of: notification.type_of,
- title: notification.title,
- message: notification.message,
+ type_of: notification.type,
+ title: notification.to_notification.title,
+ message: notification.to_notification.message,
created_at: I18n.l(notification.created_at, format: :full),
today: notification.created_at.today?,
- checked: notification.checked
+ checked: notification.read_at.present?
}
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 1f5d3ae55..6fdc6a3a5 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -101,12 +101,10 @@ module ApplicationHelper
end
def generate_annotation_notification(target_user, title, message)
- notification = Notification.create(
- type_of: :assignment,
+ GeneralNotification.with(
title: sanitize_input(title),
message: sanitize_input(message)
- )
- UserNotification.create(notification: notification, user: target_user) if target_user.assignments_notification
+ ).deliver_later(target_user)
end
def custom_link_open_new_tab(text)
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 5cce5d3e6..4635eb814 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -18,14 +18,9 @@ module NotificationsHelper
message = "#{I18n.t('search.index.team')} #{team.name}"
end
- notification = Notification.create(
- type_of: :assignment,
+ GeneralNotification.with(
title: sanitize_input(title),
message: sanitize_input(message)
- )
-
- if target_user.assignments_notification
- notification.create_user_notification(target_user)
- end
+ ).deliver_later(target_user)
end
end
diff --git a/app/jobs/concerns/failed_delivery_notifiable_job.rb b/app/jobs/concerns/failed_delivery_notifiable_job.rb
index ba5458f8b..e3b374333 100644
--- a/app/jobs/concerns/failed_delivery_notifiable_job.rb
+++ b/app/jobs/concerns/failed_delivery_notifiable_job.rb
@@ -23,12 +23,11 @@ module FailedDeliveryNotifiableJob
@user = User.find_by(id: arguments.last[:user_id])
return if @user.blank?
- notification = Notification.create!(
- type_of: :deliver_error,
+ DeliveryNotification.with(
title: failed_notification_title,
- message: failed_notification_message
- )
- notification.create_user_notification(@user)
+ message: failed_notification_message,
+ error: true
+ ).deliver_later(@user)
end
def failed_notification_title
diff --git a/app/jobs/protocols/docx_import_job.rb b/app/jobs/protocols/docx_import_job.rb
index 0bfb494c4..bfa967ef1 100644
--- a/app/jobs/protocols/docx_import_job.rb
+++ b/app/jobs/protocols/docx_import_job.rb
@@ -136,15 +136,13 @@ module Protocols
"href='#{Rails.application.routes.url_helpers.rails_blob_path(@tmp_files.take.file)}'>" \
"#{@tmp_files.take.file.filename}"
- notification = Notification.create!(
- type_of: :deliver,
+ DeliveryNotification.with(
title: I18n.t('protocols.import_export.import_protocol_notification.title', link: original_file_download_link),
- message: "#{I18n.t('protocols.import_export.import_protocol_notification.message')} " \
- "" \
- "#{@protocol.name}"
- )
- notification.create_user_notification(@user)
+ message: "#{I18n.t('protocols.import_export.import_protocol_notification.message')} " \
+ "" \
+ "#{@protocol.name}"
+ ).deliver_later(@user)
end
# Overrides method from FailedDeliveryNotifiableJob concern
diff --git a/app/jobs/reports/docx_job.rb b/app/jobs/reports/docx_job.rb
index 6f9cc6d92..ae1d5a743 100644
--- a/app/jobs/reports/docx_job.rb
+++ b/app/jobs/reports/docx_job.rb
@@ -21,16 +21,15 @@ module Reports
report.docx_ready!
report_path = Rails.application.routes.url_helpers
.reports_path(team: report.team.id, preview_report_id: report.id, preview_type: :docx)
- notification = Notification.create(
- type_of: :deliver,
+
+ DeliveryNotification.with(
title: I18n.t('projects.reports.index.generation.completed_docx_notification_title'),
message: I18n.t('projects.reports.index.generation.completed_notification_message',
report_link: "#{escape_input(report.name)}",
team_name: escape_input(report.team.name))
- )
+ ).deliver_later(user)
Reports::DocxPreviewJob.perform_now(report.id)
- notification.create_user_notification(user)
ensure
I18n.backend.date_format = nil
file.close
diff --git a/app/jobs/reports/pdf_job.rb b/app/jobs/reports/pdf_job.rb
index 2bce30b56..e1b43527a 100644
--- a/app/jobs/reports/pdf_job.rb
+++ b/app/jobs/reports/pdf_job.rb
@@ -60,14 +60,13 @@ module Reports
report_path = Rails.application.routes.url_helpers
.reports_path(team: report.team.id, preview_report_id: report.id, preview_type: :pdf)
- notification = Notification.create(
- type_of: :deliver,
+
+ DeliveryNotification.with(
title: I18n.t('projects.reports.index.generation.completed_pdf_notification_title'),
message: I18n.t('projects.reports.index.generation.completed_notification_message',
report_link: "#{escape_input(report.name)}",
team_name: escape_input(report.team.name))
- )
- notification.create_user_notification(user)
+ ).deliver_later(user)
ensure
I18n.backend.date_format = nil
file.close(true)
diff --git a/app/jobs/repositories_export_job.rb b/app/jobs/repositories_export_job.rb
index 911334621..4d93eeab6 100644
--- a/app/jobs/repositories_export_job.rb
+++ b/app/jobs/repositories_export_job.rb
@@ -83,18 +83,16 @@ class RepositoriesExportJob < ApplicationJob
end
def generate_notification
- notification = Notification.create!(
- type_of: :deliver,
+ DeliveryNotification.with(
title: I18n.t('zip_export.notification_title'),
- message: "" \
- "#{@zip_export.zip_file_name}"
- )
- notification.create_user_notification(@user)
+ message: "" \
+ "#{@zip_export.zip_file_name}"
+ ).deliver_later(@user)
end
# Overrides method from FailedDeliveryNotifiableJob concern
diff --git a/app/jobs/zip_export_job.rb b/app/jobs/zip_export_job.rb
index fbbb2e47c..8620a0fbd 100644
--- a/app/jobs/zip_export_job.rb
+++ b/app/jobs/zip_export_job.rb
@@ -34,17 +34,15 @@ class ZipExportJob < ApplicationJob
end
def generate_notification!
- notification = Notification.create!(
- type_of: :deliver,
+ DeliveryNotification.with(
title: I18n.t('zip_export.notification_title'),
- message: "" \
"#{@zip_export.zip_file_name}"
- )
- notification.create_user_notification(@user)
+ ).deliver_later(@user)
end
end
diff --git a/app/models/concerns/generate_notification_model.rb b/app/models/concerns/generate_notification_model.rb
index deb443a06..ed8c1335b 100644
--- a/app/models/concerns/generate_notification_model.rb
+++ b/app/models/concerns/generate_notification_model.rb
@@ -14,15 +14,11 @@ module GenerateNotificationModel
message = generate_activity_content(self, no_links: true, no_custom_links: true)
description = generate_notification_description_elements(subject).reverse.join(' | ')
- notification = Notification.create(
- type_of: notification_type,
- title: sanitize_input(message),
- message: sanitize_input(description),
- generator_user_id: owner.id
- )
-
notification_recipients.each do |user|
- notification.create_user_notification(user)
+ ActivityNotification.with(
+ title: sanitize_input(message),
+ message: sanitize_input(description)
+ ).deliver_later(user)
end
end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index dd6829ec2..d0a4f3919 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -5,16 +5,11 @@ class Notification < ApplicationRecord
has_many :users, through: :user_notifications
belongs_to :generator_user, class_name: 'User', optional: true
+ include Noticed::Model
+ belongs_to :recipient, polymorphic: true
+
enum type_of: Extends::NOTIFICATIONS_TYPES
- def create_user_notification(user)
- return if user == generator_user
- return unless can_send_to_user?(user)
- return unless user.enabled_notifications_for?(type_of.to_sym, :web)
-
- user_notifications.create!(user: user)
- end
-
private
def can_send_to_user?(_user)
diff --git a/app/models/user.rb b/app/models/user.rb
index d659da492..0ee836291 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -307,8 +307,7 @@ class User < ApplicationRecord
inverse_of: :created_by,
dependent: :destroy
- has_many :user_notifications, inverse_of: :user
- has_many :notifications, through: :user_notifications
+ has_many :notifications, as: :recipient, dependent: :destroy, inverse_of: :recipient
has_many :zip_exports, inverse_of: :user, dependent: :destroy
has_many :view_states, dependent: :destroy
diff --git a/app/notifications/activity_notification.rb b/app/notifications/activity_notification.rb
new file mode 100644
index 000000000..1b6ec34e8
--- /dev/null
+++ b/app/notifications/activity_notification.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# To deliver this notification:
+#
+# ActivityNotification.with(post: @post).deliver_later(current_user)
+# ActivityNotification.with(post: @post).deliver(current_user)
+
+class ActivityNotification < Noticed::Base
+ # Add your delivery methods
+ #
+ deliver_by :database
+ # deliver_by :email, mailer: "UserMailer"
+ # deliver_by :slack
+ # deliver_by :custom, class: "MyDeliveryMethod"
+
+ # Add required params
+ #
+ # param :post
+
+ def message
+ # if params[:legacy]
+ params[:message]
+ #else
+ # new logic
+ # end
+ end
+
+ def title
+ # if params[:legacy]
+ params[:title]
+ # else
+ # new logic
+ # end
+ end
+ # def url
+ # post_path(params[:post])
+ # end
+end
diff --git a/app/notifications/delivery_notification.rb b/app/notifications/delivery_notification.rb
new file mode 100644
index 000000000..a161df661
--- /dev/null
+++ b/app/notifications/delivery_notification.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# To deliver this notification:
+#
+# DeliveryNotification.with(post: @post).deliver_later(current_user)
+# DeliveryNotification.with(post: @post).deliver(current_user)
+
+class DeliveryNotification < Noticed::Base
+ # Add your delivery methods
+ #
+ deliver_by :database
+ # deliver_by :email, mailer: "UserMailer"
+ # deliver_by :slack
+ # deliver_by :custom, class: "MyDeliveryMethod"
+
+ # Add required params
+ #
+ # param :post
+
+ # Define helper methods to make rendering easier.
+ #
+ def message
+ # if params[:legacy]
+ params[:message]
+ # else
+ # new logic
+ # end
+ end
+
+ def title
+ # if params[:legacy]
+ params[:title]
+ # else
+ # new logic
+ # end
+ end
+end
diff --git a/app/notifications/general_notification.rb b/app/notifications/general_notification.rb
new file mode 100644
index 000000000..375224df1
--- /dev/null
+++ b/app/notifications/general_notification.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+# To deliver this notification:
+#
+# GeneralNotification.with(post: @post).deliver_later(current_user)
+# GeneralNotification.with(post: @post).deliver(current_user)
+
+class GeneralNotification < Noticed::Base
+ # Add your delivery methods
+ #
+ deliver_by :database
+ # deliver_by :email, mailer: "UserMailer"
+ # deliver_by :slack
+ # deliver_by :custom, class: "MyDeliveryMethod"
+
+ # Add required params
+ #
+ # param :post
+
+ # Define helper methods to make rendering easier.
+ #
+ def message
+ # if params[:legacy]
+ params[:message]
+ # else
+ # new logic
+ # end
+ end
+
+ def title
+ # if params[:legacy]
+ params[:title]
+ # else
+ # new logic
+ # end
+ end
+ #
+ # def url
+ # post_path(params[:post])
+ # end
+end
diff --git a/db/migrate/20231011103114_migrate_notification_to_noticed.rb b/db/migrate/20231011103114_migrate_notification_to_noticed.rb
new file mode 100644
index 000000000..e460ee6e6
--- /dev/null
+++ b/db/migrate/20231011103114_migrate_notification_to_noticed.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+class MigrateNotificationToNoticed < ActiveRecord::Migration[7.0]
+ def up
+ add_column :notifications, :params, :jsonb, default: {}, null: false
+ add_column :notifications, :type, :string, null: false, default: 'LegacyNotification'
+ add_column :notifications, :read_at, :datetime
+ add_column :notifications, :recipient_id, :bigint
+ add_column :notifications, :recipient_type, :string
+
+ type_mapping = {
+ 'assignment' => 'ActivityNotification',
+ 'recent_changes' => 'GeneralNotification',
+ 'deliver' => 'DeliveryNotification',
+ 'deliver_error' => 'DeliveryNotification'
+ }
+
+ UserNotification.all.each do |user_notification|
+ notification = user_notification.notification.dup
+
+ new_type = type_mapping[notification.type_of]
+
+ params = {
+ title: notification.title,
+ message: notification.message,
+ legacy: true
+ }
+
+ params[:error] = notification.type_of == 'deliver_error' if new_type == 'DeliveryNotification'
+ notification.update(
+ params: params,
+ type: new_type,
+ read_at: (user_notification.updated_at if user_notification.checked),
+ recipient_id: user_notification.user_id,
+ recipient_type: 'User',
+ created_at: user_notification.created_at,
+ updated_at: user_notification.updated_at
+ )
+ end
+
+ Notification.where(type: 'LegacyNotification').destroy_all
+
+ remove_column :notifications, :type_of
+ remove_column :notifications, :title
+ remove_column :notifications, :message
+ remove_column :notifications, :generator_user_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index be3be339f..b0af8dfd6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_09_04_080206) do
+ActiveRecord::Schema[7.0].define(version: 2023_10_11_103114) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gist"
enable_extension "pg_trgm"
@@ -377,12 +377,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_04_080206) do
end
create_table "notifications", force: :cascade do |t|
- t.string "title"
- t.string "message"
- t.integer "type_of", null: false
- t.bigint "generator_user_id"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
+ t.jsonb "params", default: {}, null: false
+ t.string "type", default: "LegacyNotification", null: false
+ t.datetime "read_at"
+ t.bigint "recipient_id"
+ t.string "recipient_type"
t.index ["created_at"], name: "index_notifications_on_created_at"
end
@@ -1347,7 +1348,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_04_080206) do
add_foreign_key "my_modules", "users", column: "created_by_id"
add_foreign_key "my_modules", "users", column: "last_modified_by_id"
add_foreign_key "my_modules", "users", column: "restored_by_id"
- add_foreign_key "notifications", "users", column: "generator_user_id"
add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id"
add_foreign_key "oauth_access_grants", "users", column: "resource_owner_id"
add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"