Merge pull request #6439 from scinote-eln/features/notifications-improvements

Features/notifications improvements
This commit is contained in:
Martin Artnik 2023-10-13 14:55:25 +02:00 committed by GitHub
commit 33e86d60c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 421 additions and 105 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -24,12 +24,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(@user)
end
def failed_notification_title

View file

@ -136,15 +136,13 @@ module Protocols
"href='#{Rails.application.routes.url_helpers.rails_blob_path(@tmp_files.take.file)}'>" \
"#{@tmp_files.take.file.filename}</a>"
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')} " \
"<a data-id='#{@protocol.id}' data-turbolinks='false' " \
"href='#{Rails.application.routes.url_helpers.protocol_path(@protocol)}'>" \
"#{@protocol.name}</a>"
)
notification.create_user_notification(@user)
message: "#{I18n.t('protocols.import_export.import_protocol_notification.message')} " \
"<a data-id='#{@protocol.id}' data-turbolinks='false' " \
"href='#{Rails.application.routes.url_helpers.protocol_path(@protocol)}'>" \
"#{@protocol.name}</a>"
).deliver(@user)
end
# Overrides method from FailedDeliveryNotifiableJob concern

View file

@ -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: "<a href='#{report_path}'>#{escape_input(report.name)}</a>",
team_name: escape_input(report.team.name))
)
).deliver(user)
Reports::DocxPreviewJob.perform_now(report.id)
notification.create_user_notification(user)
ensure
I18n.backend.date_format = nil
file.close

View file

@ -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: "<a href='#{report_path}'>#{escape_input(report.name)}</a>",
team_name: escape_input(report.team.name))
)
notification.create_user_notification(user)
).deliver(user)
ensure
I18n.backend.date_format = nil
file.close(true)

View file

@ -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: "<a data-id='#{@zip_export.id}' " \
"data-turbolinks='false' " \
"href='#{Rails.application
.routes
.url_helpers
.zip_exports_download_export_all_path(@zip_export)}'>" \
"#{@zip_export.zip_file_name}</a>"
)
notification.create_user_notification(@user)
message: "<a data-id='#{@zip_export.id}' " \
"data-turbolinks='false' " \
"href='#{Rails.application
.routes
.url_helpers
.zip_exports_download_export_all_path(@zip_export)}'>" \
"#{@zip_export.zip_file_name}</a>"
).deliver(@user)
end
# Overrides method from FailedDeliveryNotifiableJob concern

View file

@ -32,17 +32,15 @@ class ZipExportJob < ApplicationJob
end
def generate_notification!
notification = Notification.create!(
type_of: :deliver,
DeliveryNotification.with(
title: I18n.t('zip_export.notification_title'),
message: "<a data-id='#{@zip_export.id}' " \
message: "<a data-id='#{@zip_export.id}' " \
"data-turbolinks='false' " \
"href='#{Rails.application
.routes
.url_helpers
.zip_exports_download_path(@zip_export)}'>" \
"#{@zip_export.zip_file_name}</a>"
)
notification.create_user_notification(@user)
).deliver(@user)
end
end

View file

@ -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

View file

@ -1,19 +1,9 @@
# frozen_string_literal: true
class Notification < ApplicationRecord
has_many :user_notifications, inverse_of: :notification, dependent: :destroy
has_many :users, through: :user_notifications
belongs_to :generator_user, class_name: 'User', optional: 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
include Noticed::Model
belongs_to :recipient, polymorphic: true
private

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -14,13 +14,6 @@ class Extends
# Extends enum types. Should not be freezed, as modules might append to this.
# !!!Check all addons for the correct order!!!
# DEPRECATED 'system_message' in (SCI-2952, kept b/c of integer enums)
NOTIFICATIONS_TYPES = { assignment: 0,
recent_changes: 1,
system_message: 2, # DEPRECATED
deliver: 5,
deliver_error: 7 }
TASKS_STATES = { uncompleted: 0,
completed: 1 }

View file

@ -0,0 +1,155 @@
class NotificationExtends
NOTIFICATIONS_TYPES = {
designate_user_to_my_module_activity: {
code: 13,
recipients_module: :MyModuleDesignatedRecipients
},
undesignate_user_from_my_module_activity: {
code: 14,
recipients_module: :MyModuleDesignatedRecipients
},
my_module_due_date_reminder: {
recipients_module: :MyModuleDesignatedRecipients
},
add_comment_to_module_activity: {
code: 35,
recipients_module: :MyModuleDesignatedRecipients
},
edit_module_comment_activity: {
code: 36,
recipients_module: :MyModuleDesignatedRecipients
},
delete_module_comment_activity: {
code: 37,
recipients_module: :MyModuleDesignatedRecipients
},
add_comment_to_step_activity: {
code: 17,
recipients_module: :MyModuleDesignatedRecipients
},
edit_step_comment_activity: {
code: 38,
recipients_module: :MyModuleDesignatedRecipients
},
delete_step_comment_activity: {
code: 39,
recipients_module: :MyModuleDesignatedRecipients
},
add_comment_to_result_activity: {
code: 24,
recipients_module: :MyModuleDesignatedRecipients
},
edit_result_comment_activity: {
code: 40,
recipients_module: :MyModuleDesignatedRecipients
},
delete_result_comment_activity: {
code: 41,
recipients_module: :MyModuleDesignatedRecipients
},
assign_user_to_project_activity: {
code: 5,
recipients_module: :AssignedRecipients
},
unassign_user_from_project_activity: {
code: 7,
recipients_module: :AssignedRecipients
},
project_grant_access_to_all_team_members_activity: {
code: 242,
recipients_module: :AssignedGroupRecipients
},
project_remove_access_from_all_team_members_activity: {
code: 243,
recipients_module: :AssignedGroupRecipients
},
change_user_role_on_project_activity: {
code: 6,
recipients_module: :AssignedRecipients
},
change_user_role_on_experiment_activity: {
code: 165,
recipients_module: :AssignedRecipients
},
change_user_role_on_my_module_activity: {
code: 166,
recipients_module: :AssignedRecipients
},
item_low_stock_reminder: {
recipients_module: :ItemCreatorRecipients
},
item_date_reminder: {
recipients_module: :ItemCreatorRecipients
},
smart_annotation_added: {
recipients_module: :AnnotatedRecipients
},
invite_user_to_team_activity: {
code: 92,
recipients_module: :AssignedRecipients
},
remove_user_from_team_activity: {
code: 93,
recipients_module: :AssignedRecipients
},
change_users_role_on_team_activity: {
code: 94,
recipients_module: :AssignedRecipients
}
}
NOTIFICATIONS_GROUPS = {
my_module: {
designation: %I[
designate_user_to_my_module_activity
undesignate_user_from_my_module_activity
],
modified: %I[],
due_date: %I[
my_module_due_date_reminder
],
comments: %I[
add_comment_to_module_activity
edit_module_comment_activity
delete_module_comment_activity
add_comment_to_step_activity
edit_step_comment_activity
delete_step_comment_activity
add_comment_to_result_activity
edit_result_comment_activity
delete_result_comment_activity
]
},
project_experiment: {
access: %I[
assign_user_to_project_activity
unassign_user_from_project_activity
project_grant_access_to_all_team_members_activity
project_remove_access_from_all_team_members_activity
],
role_change: %I[
change_user_role_on_project_activity
change_user_role_on_experiment_activity
change_user_role_on_my_module_activity
]
},
repository: {
stock: %I[
item_low_stock_reminder
],
date_reminder: %I[
item_date_reminder
]
},
other: {
smart_annotation: %I[
smart_annotation_added
],
team_invitation: %I[
invite_user_to_team_activity
remove_user_from_team_activity
change_users_role_on_team_activity
]
}
}
end

View file

@ -0,0 +1,23 @@
en:
notifications:
groups:
my_module: "Task"
project_experiment: "Project & Experiment"
repository: "Inventories & Items"
other: "Others"
sub_groups:
my_module_designation: "You are assigned to or unassigned from a task"
my_module_modified: "There is a change on assigned task"
my_module_due_date: "Due date & time on assigned task (24h before)"
my_module_comments: "A comment is added or changed on a task you are assigned to"
project_experiment_access: "You are added or removed from the Project"
project_experiment_role_change: "Your role on Project, Experiment or Task is changed"
repository_stock: "A stock gets low (low stock reminder)"
repository_date_reminder: "Date reminder"
other_smart_annotation: "You are mentioned (tagged) in any place of Scinote"
other_team_invitation: "You are invited or removed from the team"
notification:
my_module_due_date_reminder_html: "Due date for %{my_module_name} is coming up"
item_low_stock_reminder_html: "Item %{repository_item_name} is running low"
item_date_reminder_html: "Date reminder for %{repository_item_name} is coming up in %{value} %{units}"

View file

@ -0,0 +1,51 @@
# 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
add_column :notifications, :read_at, :datetime
add_reference :notifications, :recipient, polymorphic: true
type_mapping = {
0 => 'ActivityNotification',
1 => 'GeneralNotification',
5 => 'DeliveryNotification',
7 => 'DeliveryNotification'
}
UserNotification.includes(:notification).find_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 == 7 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
UserNotification.delete_all
Notification.where(type: nil).delete_all
drop_table :user_notifications
change_column_null :notifications, :type, false
remove_column :notifications, :type_of
remove_column :notifications, :title
remove_column :notifications, :message
remove_column :notifications, :generator_user_id
end
end

View file

@ -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_10_03_114337) 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,13 +377,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_03_114337) 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", null: false
t.datetime "read_at"
t.string "recipient_type"
t.bigint "recipient_id"
t.index ["created_at"], name: "index_notifications_on_created_at"
t.index ["recipient_type", "recipient_id"], name: "index_notifications_on_recipient"
end
create_table "oauth_access_grants", force: :cascade do |t|
@ -1152,17 +1154,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_03_114337) do
t.index ["user_id"], name: "index_user_my_modules_on_user_id"
end
create_table "user_notifications", force: :cascade do |t|
t.bigint "user_id"
t.bigint "notification_id"
t.boolean "checked", default: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["checked"], name: "index_user_notifications_on_checked"
t.index ["notification_id"], name: "index_user_notifications_on_notification_id"
t.index ["user_id"], name: "index_user_notifications_on_user_id"
end
create_table "user_projects", force: :cascade do |t|
t.integer "role"
t.bigint "user_id", null: false
@ -1349,7 +1340,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_03_114337) 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"
@ -1473,8 +1463,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_03_114337) do
add_foreign_key "user_my_modules", "my_modules"
add_foreign_key "user_my_modules", "users"
add_foreign_key "user_my_modules", "users", column: "assigned_by_id"
add_foreign_key "user_notifications", "notifications"
add_foreign_key "user_notifications", "users"
add_foreign_key "user_projects", "projects"
add_foreign_key "user_projects", "users"
add_foreign_key "user_projects", "users", column: "assigned_by_id"