PERF: Split skipped email logs into a seperate table.
This commit is contained in:
parent
9b84e78fdf
commit
ae8b0a517f
|
@ -1,9 +1,9 @@
|
|||
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
|
||||
import debounce from "discourse/lib/debounce";
|
||||
import EmailLog from "admin/models/email-log";
|
||||
import AdminEmailLogsController from 'admin/controllers/admin-email-logs';
|
||||
import debounce from 'discourse/lib/debounce';
|
||||
import EmailLog from 'admin/models/email-log';
|
||||
|
||||
export default AdminEmailLogsController.extend({
|
||||
filterEmailLogs: debounce(function() {
|
||||
EmailLog.findAll(this.get("filter")).then(logs => this.set("model", logs));
|
||||
}, 250).observes("filter.{user,address,type,skipped_reason}")
|
||||
EmailLog.findAll(this.get('filter')).then(logs => this.set('model', logs));
|
||||
}, 250).observes('filter.{user,address,type}')
|
||||
});
|
||||
|
|
|
@ -5,5 +5,5 @@ import EmailLog from "admin/models/email-log";
|
|||
export default AdminEmailLogsController.extend({
|
||||
filterEmailLogs: debounce(function() {
|
||||
EmailLog.findAll(this.get("filter")).then(logs => this.set("model", logs));
|
||||
}, 250).observes("filter.{user,address,type,skipped_reason}")
|
||||
}, 250).observes("filter.{user,address,type}")
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
|
||||
<td>{{text-field value=filter.skipped_reason placeholderKey="admin.email.logs.filters.skipped_reason_placeholder"}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
{{#each model as |l|}}
|
||||
|
|
|
@ -18,17 +18,17 @@ class Admin::EmailController < Admin::AdminController
|
|||
end
|
||||
|
||||
def sent
|
||||
email_logs = filter_email_logs(EmailLog.sent, params)
|
||||
email_logs = filter_logs(EmailLog, params)
|
||||
render_serialized(email_logs, EmailLogSerializer)
|
||||
end
|
||||
|
||||
def skipped
|
||||
email_logs = filter_email_logs(EmailLog.skipped, params)
|
||||
render_serialized(email_logs, EmailLogSerializer)
|
||||
skipped_email_logs = filter_logs(SkippedEmailLog, params)
|
||||
render_serialized(skipped_email_logs, SkippedEmailLogSerializer)
|
||||
end
|
||||
|
||||
def bounced
|
||||
email_logs = filter_email_logs(EmailLog.bounced, params)
|
||||
email_logs = filter_logs(EmailLog.bounced, params)
|
||||
render_serialized(email_logs, EmailLogSerializer)
|
||||
end
|
||||
|
||||
|
@ -137,20 +137,20 @@ class Admin::EmailController < Admin::AdminController
|
|||
|
||||
private
|
||||
|
||||
def filter_email_logs(email_logs, params)
|
||||
email_logs = email_logs.includes(:user, post: :topic)
|
||||
def filter_logs(logs, params)
|
||||
table_name = logs.table_name
|
||||
|
||||
logs = logs.includes(:user, post: :topic)
|
||||
.references(:user)
|
||||
.order(created_at: :desc)
|
||||
.offset(params[:offset] || 0)
|
||||
.limit(50)
|
||||
|
||||
email_logs = email_logs.where("users.username ILIKE ?", "%#{params[:user]}%") if params[:user].present?
|
||||
email_logs = email_logs.where("email_logs.to_address ILIKE ?", "%#{params[:address]}%") if params[:address].present?
|
||||
email_logs = email_logs.where("email_logs.email_type ILIKE ?", "%#{params[:type]}%") if params[:type].present?
|
||||
email_logs = email_logs.where("email_logs.reply_key ILIKE ?", "%#{params[:reply_key]}%") if params[:reply_key].present?
|
||||
email_logs = email_logs.where("email_logs.skipped_reason ILIKE ?", "%#{params[:skipped_reason]}%") if params[:skipped_reason].present?
|
||||
|
||||
email_logs
|
||||
logs = logs.where("users.username ILIKE ?", "%#{params[:user]}%") if params[:user].present?
|
||||
logs = logs.where("#{table_name}.to_address ILIKE ?", "%#{params[:address]}%") if params[:address].present?
|
||||
logs = logs.where("#{table_name}.email_type ILIKE ?", "%#{params[:type]}%") if params[:type].present?
|
||||
logs = logs.where("#{table_name}.reply_key ILIKE ?", "%#{params[:reply_key]}%") if params[:reply_key].present?
|
||||
logs
|
||||
end
|
||||
|
||||
def filter_incoming_emails(incoming_emails, params)
|
||||
|
|
|
@ -42,17 +42,26 @@ module Jobs
|
|||
users.find_each do |user|
|
||||
if Guardian.new(user).can_see?(post)
|
||||
if EmailLog.reached_max_emails?(user)
|
||||
skip(user.email, user.id, post.id, I18n.t('email_log.exceeded_emails_limit'))
|
||||
skip(user.email, user.id, post.id,
|
||||
SkippedEmailLog.reason_types[:exceeded_emails_limit]
|
||||
)
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
if user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold
|
||||
skip(user.email, user.id, post.id, I18n.t('email_log.exceeded_bounces_limit'))
|
||||
skip(user.email, user.id, post.id,
|
||||
SkippedEmailLog.reason_types[:exceeded_bounces_limit]
|
||||
)
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
if (user.id == post.user_id) && (user.user_option.mailing_list_mode_frequency == 2)
|
||||
skip(user.email, user.id, post.id, I18n.t('email_log.no_echo_mailing_list_mode'))
|
||||
skip(user.email, user.id, post.id,
|
||||
SkippedEmailLog.reason_types[:mailing_list_no_echo_mode]
|
||||
)
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
|
@ -70,14 +79,13 @@ module Jobs
|
|||
|
||||
end
|
||||
|
||||
def skip(to_address, user_id, post_id, reason)
|
||||
EmailLog.create!(
|
||||
def skip(to_address, user_id, post_id, reason_type)
|
||||
SkippedEmailLog.create!(
|
||||
email_type: 'mailing_list',
|
||||
to_address: to_address,
|
||||
user_id: user_id,
|
||||
post_id: post_id,
|
||||
skipped: true,
|
||||
skipped_reason: "[MailingList] #{reason}"
|
||||
reason_type: reason_type
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,18 +20,21 @@ module Jobs
|
|||
|
||||
set_skip_context(type, args[:user_id], to_address, args[:post_id])
|
||||
|
||||
return skip(I18n.t("email_log.no_user", user_id: args[:user_id])) unless user
|
||||
return skip(SkippedEmailLog.reason_types[:user_email_no_user]) unless user
|
||||
|
||||
if args[:post_id].present?
|
||||
post = Post.find_by(id: args[:post_id])
|
||||
return skip(I18n.t('email_log.post_not_found', post_id: args[:post_id])) unless post.present?
|
||||
|
||||
unless post.present?
|
||||
return skip(SkippedEmailLog.reason_types[:user_email_post_not_found])
|
||||
end
|
||||
end
|
||||
|
||||
if args[:notification_id].present?
|
||||
notification = Notification.find_by(id: args[:notification_id])
|
||||
end
|
||||
|
||||
message, skip_reason = message_for_email(
|
||||
message, skip_reason_type = message_for_email(
|
||||
user,
|
||||
post,
|
||||
type,
|
||||
|
@ -42,7 +45,7 @@ module Jobs
|
|||
if message
|
||||
Email::Sender.new(message, type, user).send
|
||||
else
|
||||
skip_reason
|
||||
skip_reason_type
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -68,10 +71,12 @@ module Jobs
|
|||
|
||||
set_skip_context(type, user.id, to_address || user.email, post.try(:id))
|
||||
|
||||
return skip_message(I18n.t("email_log.anonymous_user")) if user.anonymous?
|
||||
if user.anonymous?
|
||||
return skip_message(SkippedEmailLog.reason_types[:user_email_anonymous_user])
|
||||
end
|
||||
|
||||
if user.suspended? && !["user_private_message", "account_suspended"].include?(type.to_s)
|
||||
return skip_message(I18n.t("email_log.suspended_not_pm"))
|
||||
return skip_message(SkippedEmailLog.reason_types[:user_email_user_suspended_not_pm])
|
||||
end
|
||||
|
||||
return if user.staged && type.to_s == "digest"
|
||||
|
@ -81,8 +86,10 @@ module Jobs
|
|||
|
||||
email_args = {}
|
||||
|
||||
if post || notification || notification_type
|
||||
return skip_message(I18n.t('email_log.seen_recently')) if seen_recently && !user.suspended?
|
||||
if (post || notification || notification_type) &&
|
||||
(seen_recently && !user.suspended?)
|
||||
|
||||
return skip_message(SkippedEmailLog.reason_types[:user_email_seen_recently])
|
||||
end
|
||||
|
||||
email_args[:post] = post if post
|
||||
|
@ -108,13 +115,13 @@ module Jobs
|
|||
|
||||
unless user.user_option.email_always?
|
||||
if (notification && notification.read?) || (post && post.seen?(user))
|
||||
return skip_message(I18n.t('email_log.notification_already_read'))
|
||||
return skip_message(SkippedEmailLog.reason_types[:user_email_notification_already_read])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
skip_reason = skip_email_for_post(post, user)
|
||||
return skip_message(skip_reason) if skip_reason.present?
|
||||
skip_reason_type = skip_email_for_post(post, user)
|
||||
return skip_message(skip_reason_type) if skip_reason_type.present?
|
||||
|
||||
# Make sure that mailer exists
|
||||
raise Discourse::InvalidParameters.new("type=#{type}") unless UserNotifications.respond_to?(type)
|
||||
|
@ -123,11 +130,11 @@ module Jobs
|
|||
email_args[:new_email] = user.email if type.to_s == "notify_old_email"
|
||||
|
||||
if EmailLog.reached_max_emails?(user, type.to_s)
|
||||
return skip_message(I18n.t('email_log.exceeded_emails_limit'))
|
||||
return skip_message(SkippedEmailLog.reason_types[:exceeded_emails_limit])
|
||||
end
|
||||
|
||||
if !EmailLog::CRITICAL_EMAIL_TYPES.include?(type.to_s) && user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold
|
||||
return skip_message(I18n.t('email_log.exceeded_bounces_limit'))
|
||||
return skip_message(SkippedEmailLog.reason_types[:exceeded_bounces_limit])
|
||||
end
|
||||
|
||||
if args[:user_history_id]
|
||||
|
@ -169,26 +176,38 @@ module Jobs
|
|||
# If this email has a related post, don't send an email if it's been deleted or seen recently.
|
||||
def skip_email_for_post(post, user)
|
||||
if post
|
||||
return I18n.t('email_log.topic_nil') if post.topic.blank?
|
||||
return I18n.t('email_log.post_user_deleted') if post.user.blank?
|
||||
return I18n.t('email_log.post_deleted') if post.user_deleted?
|
||||
return I18n.t('email_log.user_suspended') if user.suspended? && !post.user&.staff?
|
||||
if post.topic.blank?
|
||||
return SkippedEmailLog.reason_types[:user_email_topic_nil]
|
||||
end
|
||||
|
||||
if post.user.blank?
|
||||
return SkippedEmailLog.reason_types[:user_email_post_user_deleted]
|
||||
end
|
||||
|
||||
if post.user_deleted?
|
||||
return SkippedEmailLog.reason_types[:user_email_post_deleted]
|
||||
end
|
||||
|
||||
if user.suspended? && !post.user&.staff?
|
||||
return SkippedEmailLog.reason_types[:user_email_user_suspended]
|
||||
end
|
||||
|
||||
already_read = !user.user_option.email_always? && PostTiming.exists?(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id)
|
||||
return I18n.t('email_log.already_read') if already_read
|
||||
if already_read
|
||||
return SkippedEmailLog.reason_types[:user_email_already_read]
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def skip(reason)
|
||||
EmailLog.create!(
|
||||
def skip(reason_type)
|
||||
SkippedEmailLog.create!(
|
||||
email_type: @skip_context[:type],
|
||||
to_address: @skip_context[:to_address],
|
||||
user_id: @skip_context[:user_id],
|
||||
post_id: @skip_context[:post_id],
|
||||
skipped: true,
|
||||
skipped_reason: "[UserEmail] #{reason}",
|
||||
reason_type: reason_type
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ module Jobs
|
|||
EmailLog.where(reply_key: nil)
|
||||
.where("created_at < ?", threshold)
|
||||
.delete_all
|
||||
|
||||
SkippedEmailLog.where("created_at < ?", threshold).delete_all
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
class SkippedEmailLog < ActiveRecord::Base
|
||||
belongs_to :email_log
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :post
|
||||
has_one :topic, through: :post
|
||||
|
||||
validates :email_type, :to_address, :reason_type, presence: true
|
||||
|
||||
validates :custom_reason, presence: true, if: -> { is_custom? }
|
||||
validates :custom_reason, absence: true, if: -> { !is_custom? }
|
||||
validate :ensure_valid_reason_type
|
||||
|
||||
def self.reason_types
|
||||
Enum.new(
|
||||
custom: 1,
|
||||
exceeded_emails_limit: 2,
|
||||
exceeded_bounces_limit: 3,
|
||||
mailing_list_no_echo_mode: 4,
|
||||
user_email_no_user: 5,
|
||||
user_email_post_not_found: 6,
|
||||
user_email_anonymous_user: 7,
|
||||
user_email_user_suspended_not_pm: 8,
|
||||
user_email_seen_recently: 9,
|
||||
user_email_notification_already_read: 10,
|
||||
user_email_topic_nil: 11,
|
||||
user_email_post_user_deleted: 12,
|
||||
user_email_post_deleted: 13,
|
||||
user_email_user_suspended: 14,
|
||||
user_email_already_read: 15,
|
||||
sender_message_blank: 16,
|
||||
sender_message_to_blank: 17,
|
||||
sender_text_part_body_blank: 18,
|
||||
sender_body_blank: 19
|
||||
)
|
||||
end
|
||||
|
||||
def reason
|
||||
if is_custom?
|
||||
self.custom_reason
|
||||
else
|
||||
type = self.reason_type
|
||||
|
||||
I18n.t(
|
||||
"skipped_email_log.#{SkippedEmailLog.reason_types[type]}",
|
||||
user_id: self.user_id,
|
||||
post_id: self.post_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def is_custom?
|
||||
self.reason_type == self.class.reason_types[:custom]
|
||||
end
|
||||
|
||||
def ensure_valid_reason_type
|
||||
unless self.class.reason_types[self.reason_type]
|
||||
self.errors.add(:reason_type, :invalid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: skipped_email_logs
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# email_type :string not null
|
||||
# to_address :string not null
|
||||
# user_id :integer
|
||||
# post_id :integer
|
||||
# reason_type :integer not null
|
||||
# custom_reason :text
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_skipped_email_logs_on_created_at (created_at)
|
||||
# index_skipped_email_logs_on_post_id (post_id)
|
||||
# index_skipped_email_logs_on_reason_type (reason_type)
|
||||
# index_skipped_email_logs_on_user_id (user_id)
|
||||
#
|
|
@ -0,0 +1,29 @@
|
|||
module EmailLogsMixin
|
||||
def self.included(klass)
|
||||
klass.attributes :id,
|
||||
:to_address,
|
||||
:email_type,
|
||||
:user_id,
|
||||
:created_at,
|
||||
:post_url,
|
||||
:post_description
|
||||
|
||||
klass.has_one :user, serializer: BasicUserSerializer, embed: :objects
|
||||
end
|
||||
|
||||
def post_url
|
||||
object.post.url
|
||||
end
|
||||
|
||||
def include_post_url?
|
||||
object.post.present?
|
||||
end
|
||||
|
||||
def include_post_description?
|
||||
object.post.present? && object.post.topic.present?
|
||||
end
|
||||
|
||||
def post_description
|
||||
"#{object.post.topic.title} ##{object.post.post_number}"
|
||||
end
|
||||
end
|
|
@ -1,37 +1,8 @@
|
|||
class EmailLogSerializer < ApplicationSerializer
|
||||
include EmailLogsMixin
|
||||
|
||||
attributes :id,
|
||||
:reply_key,
|
||||
:to_address,
|
||||
:email_type,
|
||||
:user_id,
|
||||
:created_at,
|
||||
:skipped,
|
||||
:skipped_reason,
|
||||
:post_url,
|
||||
:post_description,
|
||||
attributes :reply_key,
|
||||
:bounced
|
||||
|
||||
has_one :user, serializer: BasicUserSerializer, embed: :objects
|
||||
|
||||
def include_skipped_reason?
|
||||
object.skipped
|
||||
end
|
||||
|
||||
def post_url
|
||||
object.post.url
|
||||
end
|
||||
|
||||
def include_post_url?
|
||||
object.post.present?
|
||||
end
|
||||
|
||||
def include_post_description?
|
||||
object.post.present? && object.post.topic.present?
|
||||
end
|
||||
|
||||
def post_description
|
||||
"#{object.post.topic.title} ##{object.post.post_number}"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
class SkippedEmailLogSerializer < ApplicationSerializer
|
||||
include EmailLogsMixin
|
||||
|
||||
attributes :skipped_reason
|
||||
|
||||
def skipped_reason
|
||||
object.reason
|
||||
end
|
||||
end
|
|
@ -3352,7 +3352,6 @@ en:
|
|||
address_placeholder: "name@example.com"
|
||||
type_placeholder: "digest, signup..."
|
||||
reply_key_placeholder: "reply key"
|
||||
skipped_reason_placeholder: "reason"
|
||||
|
||||
moderation_history:
|
||||
performed_by: "Performed By"
|
||||
|
|
|
@ -3163,25 +3163,25 @@ en:
|
|||
sockpuppet: "A new user created a topic, and another new user at the same IP address (%{ip_address}) replied. See the <a href='/admin/site_settings/category/spam'>`flag_sockpuppets`</a> site setting."
|
||||
spam_hosts: "This new user tried to create multiple posts with links to the same domain (%{domain}). See the <a href='/admin/site_settings/category/spam'>`newuser_spam_host_threshold`</a> site setting."
|
||||
|
||||
email_log:
|
||||
post_user_deleted: "User of the post has been deleted."
|
||||
no_user: "Can't find user with id %{user_id}"
|
||||
anonymous_user: "User is anonymous"
|
||||
suspended_not_pm: "User is suspended, not a message"
|
||||
seen_recently: "User was seen recently"
|
||||
post_not_found: "Can't find a post with id %{post_id}"
|
||||
notification_already_read: "The notification this email is about has already been read"
|
||||
topic_nil: "post.topic is nil"
|
||||
post_deleted: "post was deleted by the author"
|
||||
user_suspended: "user was suspended"
|
||||
already_read: "user has already read this post"
|
||||
skipped_email_log:
|
||||
exceeded_emails_limit: "Exceeded max_emails_per_day_per_user"
|
||||
exceeded_bounces_limit: "Exceeded bounce_score_threshold"
|
||||
message_blank: "message is blank"
|
||||
message_to_blank: "message.to is blank"
|
||||
text_part_body_blank: "text_part.body is blank"
|
||||
body_blank: "body is blank"
|
||||
no_echo_mailing_list_mode: "Mailing list notifications disabled for user's own posts"
|
||||
mailing_list_no_echo_mode: "Mailing list notifications disabled for user's own posts"
|
||||
user_email_no_user: "Can't find user with id %{user_id}"
|
||||
user_email_post_not_found: "Can't find a post with id %{post_id}"
|
||||
user_email_anonymous_user: "User is anonymous"
|
||||
user_email_user_suspended_not_pm: "User is suspended, not a message"
|
||||
user_email_seen_recently: "User was seen recently"
|
||||
user_email_notification_already_read: "The notification this email is about has already been read"
|
||||
user_email_notification_topic_nil: "post.topic is nil"
|
||||
user_email_post_user_deleted: "User of the post has been deleted."
|
||||
user_email_post_deleted: "post was deleted by the author"
|
||||
user_email_user_suspended: "user was suspended"
|
||||
user_email_already_read: "user has already read this post"
|
||||
sender_message_blank: "message is blank"
|
||||
sender_message_to_blank: "message.to is blank"
|
||||
sender_text_part_body_blank: "text_part.body is blank"
|
||||
sender_text_body_blank: "body is blank"
|
||||
|
||||
color_schemes:
|
||||
base_theme_name: "Base"
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
require 'migration/column_dropper'
|
||||
|
||||
class CreateSkippedEmailLogs < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :skipped_email_logs do |t|
|
||||
t.string :email_type, null: false
|
||||
t.string :to_address, null: false
|
||||
t.integer :user_id
|
||||
t.integer :post_id
|
||||
t.integer :reason_type, null: false
|
||||
t.text :custom_reason
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :skipped_email_logs, :created_at
|
||||
add_index :skipped_email_logs, :user_id
|
||||
add_index :skipped_email_logs, :post_id
|
||||
add_index :skipped_email_logs, :reason_type
|
||||
|
||||
sql = <<~SQL
|
||||
INSERT INTO skipped_email_logs (
|
||||
email_type,
|
||||
to_address,
|
||||
user_id,
|
||||
post_id,
|
||||
reason_type,
|
||||
custom_reason,
|
||||
created_at,
|
||||
updated_at
|
||||
) SELECT
|
||||
email_type,
|
||||
to_address,
|
||||
user_id,
|
||||
post_id,
|
||||
1,
|
||||
skipped_reason,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM email_logs
|
||||
WHERE skipped IS TRUE
|
||||
SQL
|
||||
|
||||
execute(sql)
|
||||
|
||||
Migration::ColumnDropper.mark_readonly('email_logs', 'skipped_reason')
|
||||
end
|
||||
end
|
|
@ -27,8 +27,8 @@ module Email
|
|||
return if ActionMailer::Base::NullMail === @message
|
||||
return if ActionMailer::Base::NullMail === (@message.message rescue nil)
|
||||
|
||||
return skip(I18n.t('email_log.message_blank')) if @message.blank?
|
||||
return skip(I18n.t('email_log.message_to_blank')) if @message.to.blank?
|
||||
return skip(SkippedEmailLog.reason_types[:sender_message_blank]) if @message.blank?
|
||||
return skip(SkippedEmailLog.reason_types[:sender_message_to_blank]) if @message.to.blank?
|
||||
|
||||
if SiteSetting.disable_emails == "non-staff"
|
||||
user = User.find_by_email(to_address)
|
||||
|
@ -36,9 +36,13 @@ module Email
|
|||
end
|
||||
|
||||
if @message.text_part
|
||||
return skip(I18n.t('email_log.text_part_body_blank')) if @message.text_part.body.to_s.blank?
|
||||
if @message.text_part.body.to_s.blank?
|
||||
return skip(SkippedEmailLog.reason_types[:sender_text_part_body_blank])
|
||||
end
|
||||
else
|
||||
return skip(I18n.t('email_log.body_blank')) if @message.body.to_s.blank?
|
||||
if @message.body.to_s.blank?
|
||||
return skip(SkippedEmailLog.reason_types[:sender_body_blank])
|
||||
end
|
||||
end
|
||||
|
||||
@message.charset = 'UTF-8'
|
||||
|
@ -186,7 +190,7 @@ module Email
|
|||
begin
|
||||
@message.deliver_now
|
||||
rescue *SMTP_CLIENT_ERRORS => e
|
||||
return skip(e.message)
|
||||
return skip(SkippedEmailLog.reason_types[:custom], custom_reason: e.message)
|
||||
end
|
||||
|
||||
# Save and return the email log
|
||||
|
@ -222,14 +226,16 @@ module Email
|
|||
header.value
|
||||
end
|
||||
|
||||
def skip(reason)
|
||||
EmailLog.create!(
|
||||
def skip(reason_type, custom_reason: nil)
|
||||
attributes = {
|
||||
email_type: @email_type,
|
||||
to_address: to_address,
|
||||
user_id: @user.try(:id),
|
||||
skipped: true,
|
||||
skipped_reason: "[Sender] #{reason}"
|
||||
)
|
||||
user_id: @user&.id,
|
||||
reason_type: reason_type
|
||||
}
|
||||
|
||||
attributes[:custom_reason] = custom_reason if custom_reason
|
||||
SkippedEmailLog.create!(attributes)
|
||||
end
|
||||
|
||||
def merge_json_x_header(name, value)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Fabricator(:skipped_email_log) do
|
||||
to_address { sequence(:address) { |i| "blah#{i}@example.com" } }
|
||||
email_type :invite
|
||||
reason_type SkippedEmailLog.reason_types[:exceeded_emails_limit]
|
||||
end
|
|
@ -7,17 +7,22 @@ describe Jobs::CleanUpEmailLogs do
|
|||
Fabricate(:email_log, created_at: 2.years.ago)
|
||||
Fabricate(:email_log, created_at: 2.weeks.ago)
|
||||
Fabricate(:email_log, created_at: 2.days.ago)
|
||||
|
||||
Fabricate(:skipped_email_log, created_at: 2.years.ago)
|
||||
Fabricate(:skipped_email_log)
|
||||
end
|
||||
|
||||
it "removes old email logs without a reply_key" do
|
||||
Jobs::CleanUpEmailLogs.new.execute({})
|
||||
expect(EmailLog.count).to eq(3)
|
||||
expect(SkippedEmailLog.count).to eq(1)
|
||||
end
|
||||
|
||||
it "does not remove old email logs when delete_email_logs_after_days is 0" do
|
||||
SiteSetting.delete_email_logs_after_days = 0
|
||||
Jobs::CleanUpEmailLogs.new.execute({})
|
||||
expect(EmailLog.count).to eq(4)
|
||||
expect(SkippedEmailLog.count).to eq(2)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -129,7 +129,13 @@ describe Jobs::NotifyMailingListSubscribers do
|
|||
Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id)
|
||||
UserNotifications.expects(:mailing_list_notify).with(mailing_list_user, post).never
|
||||
|
||||
expect(EmailLog.where(user: mailing_list_user, skipped: true).count).to eq(1)
|
||||
expect(SkippedEmailLog.exists?(
|
||||
email_type: "mailing_list",
|
||||
user: mailing_list_user,
|
||||
post: post,
|
||||
to_address: mailing_list_user.email,
|
||||
reason_type: SkippedEmailLog.reason_types[:exceeded_emails_limit]
|
||||
)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -141,7 +147,13 @@ describe Jobs::NotifyMailingListSubscribers do
|
|||
Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id)
|
||||
UserNotifications.expects(:mailing_list_notify).with(mailing_list_user, post).never
|
||||
|
||||
expect(EmailLog.where(user: mailing_list_user, skipped: true).count).to eq(1)
|
||||
expect(SkippedEmailLog.exists?(
|
||||
email_type: "mailing_list",
|
||||
user: mailing_list_user,
|
||||
post: post,
|
||||
to_address: mailing_list_user.email,
|
||||
reason_type: SkippedEmailLog.reason_types[:exceeded_bounces_limit]
|
||||
)).to eq(true)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -79,38 +79,46 @@ describe Jobs::UserEmail do
|
|||
end
|
||||
|
||||
context "email_log" do
|
||||
let(:post) { Fabricate(:post) }
|
||||
|
||||
before do
|
||||
SiteSetting.editing_grace_period = 0
|
||||
Fabricate(:post)
|
||||
post
|
||||
end
|
||||
|
||||
it "creates an email log when the mail is sent (via Email::Sender)" do
|
||||
last_emailed_at = user.last_emailed_at
|
||||
|
||||
expect { Jobs::UserEmail.new.execute(type: :digest, user_id: user.id) }.to change { EmailLog.count }.by(1)
|
||||
expect do
|
||||
Jobs::UserEmail.new.execute(type: :digest, user_id: user.id,)
|
||||
end.to change { EmailLog.count }.by(1)
|
||||
|
||||
email_log = EmailLog.last
|
||||
expect(email_log.skipped).to eq(false)
|
||||
expect(email_log.user_id).to eq(user.id)
|
||||
|
||||
expect(email_log.user).to eq(user)
|
||||
expect(email_log.post).to eq(nil)
|
||||
# last_emailed_at should have changed
|
||||
expect(email_log.user.last_emailed_at).to_not eq(last_emailed_at)
|
||||
end
|
||||
|
||||
it "creates an email log when the mail is skipped" do
|
||||
it "creates a skipped email log when the mail is skipped" do
|
||||
last_emailed_at = user.last_emailed_at
|
||||
user.update_columns(suspended_till: 1.year.from_now)
|
||||
|
||||
expect { Jobs::UserEmail.new.execute(type: :digest, user_id: user.id) }.to change { EmailLog.count }.by(1)
|
||||
expect do
|
||||
Jobs::UserEmail.new.execute(type: :digest, user_id: user.id)
|
||||
end.to change { SkippedEmailLog.count }.by(1)
|
||||
|
||||
email_log = EmailLog.last
|
||||
expect(email_log.skipped).to eq(true)
|
||||
expect(email_log.skipped_reason).to be_present
|
||||
expect(email_log.user_id).to eq(user.id)
|
||||
expect(SkippedEmailLog.exists?(
|
||||
email_type: "digest",
|
||||
user: user,
|
||||
post: nil,
|
||||
to_address: user.email,
|
||||
reason_type: SkippedEmailLog.reason_types[:user_email_user_suspended_not_pm]
|
||||
)).to eq(true)
|
||||
|
||||
# last_emailed_at doesn't change
|
||||
expect(email_log.user.last_emailed_at).to eq(last_emailed_at)
|
||||
expect(user.last_emailed_at).to eq(last_emailed_at)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -205,8 +213,15 @@ describe Jobs::UserEmail do
|
|||
notification_data_hash: notification.data_hash
|
||||
)
|
||||
|
||||
expect(message).to eq nil
|
||||
expect(err.skipped_reason).to match(/notification.*already/)
|
||||
expect(message).to eq(nil)
|
||||
|
||||
expect(SkippedEmailLog.exists?(
|
||||
email_type: "user_mentioned",
|
||||
user: user,
|
||||
post: post,
|
||||
to_address: user.email,
|
||||
reason_type: SkippedEmailLog.reason_types[:user_email_notification_already_read]
|
||||
)).to eq(true)
|
||||
end
|
||||
|
||||
it "does send the email if the notification has been seen but the user is set for email_always" do
|
||||
|
@ -230,20 +245,59 @@ describe Jobs::UserEmail do
|
|||
end
|
||||
|
||||
it "does not send notification if limit is reached" do
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
|
||||
expect(EmailLog.where(user_id: user.id, skipped: true).count).to eq(1)
|
||||
expect do
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_mentioned,
|
||||
user_id: user.id,
|
||||
notification_id: notification.id,
|
||||
post_id: post.id
|
||||
)
|
||||
end.to change { SkippedEmailLog.count }.by(1)
|
||||
|
||||
expect(SkippedEmailLog.exists?(
|
||||
email_type: "user_mentioned",
|
||||
user: user,
|
||||
post: post,
|
||||
to_address: user.email,
|
||||
reason_type: SkippedEmailLog.reason_types[:exceeded_emails_limit]
|
||||
)).to eq(true)
|
||||
end
|
||||
|
||||
it "sends critical email" do
|
||||
Jobs::UserEmail.new.execute(type: :forgot_password, user_id: user.id, notification_id: notification.id, post_id: post.id)
|
||||
expect(EmailLog.where(user_id: user.id, skipped: true).count).to eq(0)
|
||||
expect do
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :forgot_password,
|
||||
user_id: user.id,
|
||||
notification_id: notification.id,
|
||||
)
|
||||
end.to change { EmailLog.count }.by(1)
|
||||
|
||||
expect(EmailLog.exists?(
|
||||
email_type: "forgot_password",
|
||||
user: user,
|
||||
)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
it "does not send notification if bounce threshold is reached" do
|
||||
user.user_stat.update(bounce_score: SiteSetting.bounce_score_threshold)
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
|
||||
expect(EmailLog.where(user_id: user.id, skipped: true).count).to eq(1)
|
||||
|
||||
expect do
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_mentioned,
|
||||
user_id: user.id,
|
||||
notification_id: notification.id,
|
||||
post_id: post.id
|
||||
)
|
||||
end.to change { SkippedEmailLog.count }.by(1)
|
||||
|
||||
expect(SkippedEmailLog.exists?(
|
||||
email_type: "user_mentioned",
|
||||
user: user,
|
||||
post: post,
|
||||
to_address: user.email,
|
||||
reason_type: SkippedEmailLog.reason_types[:exceeded_bounces_limit]
|
||||
)).to eq(true)
|
||||
end
|
||||
|
||||
it "doesn't send the mail if the user is using individual mailing list mode" do
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe SkippedEmailLog, type: :model do
|
||||
let(:custom_skipped_email_log) do
|
||||
Fabricate.build(:skipped_email_log,
|
||||
reason_type: SkippedEmailLog.reason_types[:custom]
|
||||
)
|
||||
end
|
||||
|
||||
let(:skipped_email_log) { Fabricate.build(:skipped_email_log) }
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:email_type) }
|
||||
it { is_expected.to validate_presence_of(:to_address) }
|
||||
it { is_expected.to validate_presence_of(:reason_type) }
|
||||
|
||||
describe '#reason_type' do
|
||||
describe 'when reason_type is not valid' do
|
||||
it 'should not be valid' do
|
||||
skipped_email_log.reason_type = 999999
|
||||
|
||||
expect(skipped_email_log.valid?).to eq(false)
|
||||
expect(skipped_email_log.errors.messages).to include(:reason_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#custom_reason' do
|
||||
describe 'when log is a custom reason type' do
|
||||
describe 'when custom reason is blank' do
|
||||
it 'should not be valid' do
|
||||
expect(custom_skipped_email_log.valid?).to eq(false)
|
||||
|
||||
expect(custom_skipped_email_log.errors.messages)
|
||||
.to include(:custom_reason)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when custom reason is not blank' do
|
||||
it 'should be valid' do
|
||||
custom_skipped_email_log.custom_reason = 'test'
|
||||
|
||||
expect(custom_skipped_email_log.valid?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when log is not a custom reason type' do
|
||||
describe 'when custom reason is blank' do
|
||||
it 'should be valid' do
|
||||
expect(skipped_email_log.valid?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when custom reason is not blank' do
|
||||
it 'should not be valid' do
|
||||
skipped_email_log.custom_reason = 'test'
|
||||
|
||||
expect(skipped_email_log.valid?).to eq(false)
|
||||
expect(skipped_email_log.errors.messages).to include(:custom_reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.reason_types' do
|
||||
describe "verify enum sequence" do
|
||||
it 'should return the right sequence' do
|
||||
expect(SkippedEmailLog.reason_types[:custom]).to eq(1)
|
||||
expect(SkippedEmailLog.reason_types[:user_email_already_read]).to eq(15)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reason' do
|
||||
describe 'for a custom log' do
|
||||
it 'should return the right output' do
|
||||
custom_skipped_email_log.custom_reason = 'test'
|
||||
expect(custom_skipped_email_log.reason).to eq('test')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'for a non custom log' do
|
||||
it 'should return the right output' do
|
||||
expect(skipped_email_log.reason).to eq("
|
||||
#{I18n.t('skipped_email_log.exceeded_emails_limit')}
|
||||
".strip)
|
||||
|
||||
skipped_email_log.reason_type =
|
||||
SkippedEmailLog.reason_types[:user_email_no_user]
|
||||
|
||||
skipped_email_log.user_id = 9999
|
||||
|
||||
expect(skipped_email_log.reason).to eq("
|
||||
#{I18n.t(
|
||||
'skipped_email_log.user_email_no_user', user_id: 9999
|
||||
)}
|
||||
".strip)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -39,9 +39,34 @@ describe Admin::EmailController do
|
|||
end
|
||||
|
||||
describe '#skipped' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:log1) { Fabricate(:skipped_email_log, user: user) }
|
||||
let!(:log2) { Fabricate(:skipped_email_log) }
|
||||
|
||||
it "succeeds" do
|
||||
get "/admin/email/skipped.json"
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
logs = JSON.parse(response.body)
|
||||
|
||||
expect(logs.first["id"]).to eq(log2.id)
|
||||
expect(logs.last["id"]).to eq(log1.id)
|
||||
end
|
||||
|
||||
describe 'when filtered by username' do
|
||||
it 'should return the right response' do
|
||||
get "/admin/email/skipped.json", params: {
|
||||
user: user.username
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
logs = JSON.parse(response.body)
|
||||
|
||||
expect(logs.count).to eq(1)
|
||||
expect(logs.first["id"]).to eq(log1.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -54,6 +79,7 @@ describe Admin::EmailController do
|
|||
context 'with an email address' do
|
||||
it 'enqueues a test email job' do
|
||||
post "/admin/email/test.json", params: { email_address: 'eviltrout@test.domain' }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include('eviltrout@test.domain')
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue