-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #360 from ansonK/email-send-refactor
Refactor the jobs that send email.
- Loading branch information
Showing
23 changed files
with
545 additions
and
361 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -334,6 +334,3 @@ DEPENDENCIES | |
webmock | ||
whenever | ||
will_paginate | ||
|
||
BUNDLED WITH | ||
1.10.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
module EmailAllPetitionSignatories | ||
extend ActiveSupport::Concern | ||
|
||
# | ||
# Concern to add shared functionality to ActiveJob classes that are responsible | ||
# for enqueuing send email jobs | ||
# | ||
|
||
included do | ||
queue_as :default | ||
|
||
def self.run_later_tonight(petition) | ||
requested_at = Time.current | ||
|
||
petition.set_email_requested_at_for(new.timestamp_name, to: requested_at) | ||
|
||
set(wait_until: later_tonight). | ||
perform_later(petition, requested_at.getutc.iso8601(6)) | ||
end | ||
|
||
def self.later_tonight | ||
1.day.from_now.at_midnight + rand(240).minutes + rand(60).seconds | ||
end | ||
private_class_method :later_tonight | ||
|
||
end | ||
|
||
|
||
|
||
def perform(petition, requested_at_string) | ||
@petition = petition | ||
@requested_at = requested_at_string.in_time_zone | ||
do_work! | ||
end | ||
|
||
def timestamp_name | ||
raise NotImplementedError.new "Including classes must implement #timestamp_name method" | ||
end | ||
|
||
private | ||
|
||
attr_reader :petition, :requested_at | ||
|
||
def do_work! | ||
return if petition_has_been_updated? | ||
|
||
logger.info("Starting #{self.class.name} for petition '#{petition.action}' with email requested at: #{petition_timestamp}") | ||
enqueue_send_email_jobs | ||
logger.info("Finished #{self.class.name} for petition '#{petition.action}'") | ||
|
||
end | ||
|
||
# | ||
# Batches the signataries to send emails to in groups of 1000 | ||
# and enqueues a job to do the actual sending | ||
# | ||
def enqueue_send_email_jobs | ||
signatures_to_email.find_each do |signature| | ||
email_delivery_job_class.perform_later( | ||
signature: signature, | ||
timestamp_name: timestamp_name, | ||
petition: petition, | ||
requested_at_as_string: requested_at.getutc.iso8601(6) | ||
) | ||
end | ||
end | ||
|
||
# admins can ask to send the email multiple times and each time they | ||
# ask we enqueues a new job to send out emails with a new timestamp | ||
# we want to execute only the latest job enqueued | ||
def petition_has_been_updated? | ||
(petition_timestamp - requested_at).abs > 1 | ||
end | ||
|
||
def petition_timestamp | ||
petition.get_email_requested_at_for(timestamp_name) | ||
end | ||
|
||
def signatures_to_email | ||
petition.signatures_to_email_for(timestamp_name) | ||
end | ||
|
||
# The job class that handles the actual email sending for this job type | ||
def email_delivery_job_class | ||
raise NotImplementedError.new "Including classes must implement #email_delivery_job_class method" | ||
end | ||
end | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
module EmailDelivery | ||
extend ActiveSupport::Concern | ||
|
||
# | ||
# Send a single email to a recipient informing them about a petition that they have signed | ||
# Implemented as a custom job rather than using action mailers #deliver_later so we can do | ||
# extra checking before sending the email | ||
# | ||
|
||
included do | ||
queue_as :default | ||
end | ||
|
||
def perform(signature:, timestamp_name:, petition:, | ||
requested_at_as_string:, mailer: PetitionMailer.name, logger: nil) | ||
|
||
@mailer = mailer.constantize | ||
@signature = signature | ||
@petition = petition | ||
@requested_at = requested_at_as_string.in_time_zone | ||
@timestamp_name = timestamp_name | ||
@logger = logger | ||
|
||
if can_send_email? | ||
send_email | ||
record_email_sent | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :mailer, :signature, :timestamp_name, :petition, :requested_at | ||
|
||
def can_send_email? | ||
petition_has_not_been_updated? && email_not_previously_sent? | ||
end | ||
|
||
def send_email | ||
create_email.deliver_now | ||
|
||
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Net::SMTPError, Timeout::Error => e | ||
# log that the send failed | ||
logger.info("#{e.class.name} while sending email for #{self.class.name} to: #{signature.email} for #{signature.petition.action}") | ||
|
||
# | ||
# TODO: check the error and if it is a n AWS SES rate error: | ||
# 454 Throttling failure: Maximum sending rate exceeded | ||
# 454 Throttling failure: Daily message quota exceeded | ||
# | ||
# Then reschedule the send for a day later rather than keep failing | ||
# | ||
|
||
# reraise to rerun the job later via the job retry mechanism | ||
raise e | ||
end | ||
|
||
def create_email | ||
raise NotImplementedError.new "Including classes must implement #create_email method" | ||
end | ||
|
||
def record_email_sent | ||
signature.set_email_sent_at_for timestamp_name, to: petition_timestamp | ||
end | ||
|
||
def petition_timestamp | ||
petition.get_email_requested_at_for(timestamp_name) | ||
end | ||
|
||
# We do not want to send the email if the petition has been updated | ||
# As email sending is enqueued straight after a petition has been updated | ||
def petition_has_not_been_updated? | ||
(petition_timestamp - requested_at).abs < 1 | ||
end | ||
|
||
# | ||
# Have we already sent an email for this petition version? | ||
# If we have then the timestamp for the signature will match the timestamp for the petition | ||
# | ||
def email_not_previously_sent? | ||
# check that the signature is still in the list of signatures | ||
petition.signatures_to_email_for(timestamp_name).where(id: signature.id).exists? | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
class DeliverDebateOutcomeEmailJob < ActiveJob::Base | ||
include EmailDelivery | ||
|
||
def create_email | ||
mailer.notify_signer_of_debate_outcome signature.petition, signature | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
class DeliverDebateScheduledEmailJob < ActiveJob::Base | ||
include EmailDelivery | ||
|
||
def create_email | ||
mailer.notify_signer_of_debate_scheduled signature.petition, signature | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
class DeliverThresholdResponseEmailJob < ActiveJob::Base | ||
include EmailDelivery | ||
|
||
def create_email | ||
mailer.notify_signer_of_threshold_response signature.petition, signature | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,11 @@ | ||
class EmailDebateOutcomesJob < EmailPetitionSignatories::Job | ||
def self.run_later_tonight(petition) | ||
petition.set_email_requested_at_for('debate_outcome', to: Time.current) | ||
super(petition, petition.get_email_requested_at_for('debate_outcome')) | ||
end | ||
class EmailDebateOutcomesJob < ActiveJob::Base | ||
include EmailAllPetitionSignatories | ||
|
||
def perform(petition, requested_at_string, mailer = PetitionMailer.name, logger = nil) | ||
@mailer = mailer.constantize | ||
worker(petition, requested_at_string, logger).do_work! | ||
def email_delivery_job_class | ||
DeliverDebateOutcomeEmailJob | ||
end | ||
|
||
def timestamp_name | ||
'debate_outcome' | ||
end | ||
|
||
def create_email(petition, signature) | ||
@mailer.notify_signer_of_debate_outcome(petition, signature) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,11 @@ | ||
class EmailDebateScheduledJob < EmailPetitionSignatories::Job | ||
def self.run_later_tonight(petition) | ||
petition.set_email_requested_at_for('debate_scheduled', to: Time.current) | ||
super(petition, petition.get_email_requested_at_for('debate_scheduled')) | ||
end | ||
class EmailDebateScheduledJob < ActiveJob::Base | ||
include EmailAllPetitionSignatories | ||
|
||
def perform(petition, requested_at_string, mailer = PetitionMailer.name, threshold_logger = nil) | ||
@mailer = mailer.constantize | ||
worker(petition, requested_at_string, threshold_logger).do_work! | ||
def email_delivery_job_class | ||
DeliverDebateScheduledEmailJob | ||
end | ||
|
||
def timestamp_name | ||
'debate_scheduled' | ||
end | ||
|
||
def create_email(petition, signature) | ||
@mailer.notify_signer_of_debate_scheduled(petition, signature) | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,11 @@ | ||
class EmailThresholdResponseJob < EmailPetitionSignatories::Job | ||
def self.run_later_tonight(petition, requested_at = Time.current) | ||
petition.set_email_requested_at_for('government_response', to: requested_at) | ||
super(petition, petition.get_email_requested_at_for('government_response')) | ||
end | ||
class EmailThresholdResponseJob < ActiveJob::Base | ||
include EmailAllPetitionSignatories | ||
|
||
def perform(petition, requested_at_string, mailer = PetitionMailer.name, logger = nil) | ||
@mailer = mailer.constantize | ||
worker(petition, requested_at_string, logger).do_work! | ||
def email_delivery_job_class | ||
DeliverThresholdResponseEmailJob | ||
end | ||
|
||
def timestamp_name | ||
'government_response' | ||
end | ||
|
||
def create_email(petition, signature) | ||
@mailer.notify_signer_of_threshold_response(petition, signature) | ||
end | ||
end |
Oops, something went wrong.