Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/jobs/application_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ def set_submission_logging_attributes(submission:, delivery: nil)
CurrentJobLoggingAttributes.preview = submission.preview?
CurrentJobLoggingAttributes.delivery_id = delivery&.id
CurrentJobLoggingAttributes.delivery_reference = delivery&.delivery_reference
CurrentJobLoggingAttributes.delivery_method = delivery&.delivery_method
CurrentJobLoggingAttributes.delivery_formats = delivery&.formats
end

def set_submission_batch_logging_attributes(form:, mode:, delivery:)
Expand Down
2 changes: 1 addition & 1 deletion app/jobs/delete_submissions_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def perform(*_args)

def delete_submission_data(submission)
set_submission_logging_attributes(submission:)
delivery_status = submission&.single_submission_delivery&.status
delivery_status = submission.delivery_status

files = submission.journey.completed_file_upload_questions
files.each(&:delete_from_s3)
Expand Down
2 changes: 2 additions & 0 deletions app/jobs/schedule_daily_batch_deliveries_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def perform

delivery = Delivery.create!(
delivery_schedule: :daily,
delivery_method: "email",
formats: %w[csv],
submissions: batch.submissions,
batch_begin_at:,
)
Expand Down
2 changes: 2 additions & 0 deletions app/jobs/schedule_weekly_batch_deliveries_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ def perform

delivery = Delivery.create!(
delivery_schedule: :weekly,
delivery_method: "email",
formats: %w[csv],
submissions: batch.submissions,
batch_begin_at:,
)
Expand Down
6 changes: 3 additions & 3 deletions app/jobs/send_s3_submission_job.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
class SendS3SubmissionJob < SubmissionDeliveryJob
def perform(delivery_or_submission)
delivery, submission = resolve_delivery_and_submission(delivery_or_submission)
def perform(delivery)
submission = delivery.submissions.sole
set_submission_logging_attributes(submission:, delivery:)

delivery.new_attempt!

key = S3SubmissionService.new(submission:).submit
key = S3SubmissionService.new(submission:, delivery:).submit

delivery.update!(delivery_reference: key)
record_submission_sent!
Expand Down
6 changes: 3 additions & 3 deletions app/jobs/send_submission_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ class SendSubmissionJob < SubmissionDeliveryJob

retry_on Aws::SESV2::Errors::ServiceError, wait: :polynomially_longer, attempts: TOTAL_ATTEMPTS

def perform(delivery_or_submission)
def perform(delivery)
# The job will use the locale at the time it was created. Force it to be "en" as we always send submission emails in
# English.
I18n.with_locale("en") do
delivery, submission = resolve_delivery_and_submission(delivery_or_submission)
submission = delivery.submissions.sole
set_submission_logging_attributes(submission:, delivery:)

delivery.new_attempt!

message_id = AwsSesSubmissionService.new(submission:).submit
message_id = AwsSesSubmissionService.new(submission:, delivery:).submit

delivery.update!(delivery_reference: message_id)
record_submission_sent!
Expand Down
11 changes: 0 additions & 11 deletions app/jobs/submission_delivery_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,6 @@ class SubmissionDeliveryJob < ApplicationJob

private

def resolve_delivery_and_submission(argument)
case argument
when Delivery
[argument, argument.submissions.sole]
when Submission
[argument.single_submission_delivery, argument]
else
raise ArgumentError, "Expected a Delivery or Submission, got #{argument.class}"
end
end

def record_submission_sent!
milliseconds_since_scheduled = (Time.current - scheduled_at_or_enqueued_at).in_milliseconds.round
EventLogger.log_form_event("submission_sent", { milliseconds_since_scheduled: })
Expand Down
4 changes: 4 additions & 0 deletions app/models/current_job_logging_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class CurrentJobLoggingAttributes < ActiveSupport::CurrentAttributes
:delivery_id,
:delivery_reference,
:delivery_schedule,
:delivery_method,
:delivery_formats,
:batch_begin_at,
:confirmation_email_id,
:sqs_message_id,
Expand All @@ -24,6 +26,8 @@ def as_hash
delivery_id:,
delivery_reference:,
delivery_schedule:,
delivery_method:,
delivery_formats:,
batch_begin_at:,
confirmation_email_id:,
sqs_message_id:,
Expand Down
5 changes: 5 additions & 0 deletions app/models/delivery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ class Delivery < ApplicationRecord
weekly: "weekly",
}

enum :delivery_method, {
email: "email",
s3: "s3",
}

def status
return :pending if delivered_at.nil? && failed_at.nil?
return :delivered if delivered_at.present? && failed_at.nil?
Expand Down
4 changes: 3 additions & 1 deletion app/models/form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ def initialize(form_document)
:send_daily_submission_batch,
:send_weekly_submission_batch,
:submission_email,
:submission_type,
:support_email,
:support_phone,
:support_url,
:support_url_text,
:what_happens_next_markdown,
:delivery_configurations,
to: :form_document

alias_method :id, :form_id
Expand All @@ -38,6 +38,8 @@ def payment_url_with_reference(reference)
"#{form_document.payment_url}?reference=#{reference}"
end

# Deprecated: kept for historic Submission records. Use delivery_configurations on form_document.
# Can be removed in September 2026 when historic Submissions with this field have been deleted.
def submission_format
form_document.try(:submission_format) || []
end
Expand Down
11 changes: 8 additions & 3 deletions app/models/submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,18 @@ def payment_url
form.payment_url_with_reference(reference)
end

def single_submission_delivery
deliveries.immediate.sole
def delivery_status
delivery_statuses = deliveries.map(&:status)

return :failed if delivery_statuses.include?(:failed)
return :pending if delivery_statuses.include?(:pending)

:delivered
end

def self.sent?(reference)
submission = Submission.find_by(reference: reference)
submission&.single_submission_delivery&.delivery_reference&.present?
submission&.deliveries&.immediate&.all? { |d| d.delivery_reference.present? }
end

def mode_object
Expand Down
8 changes: 8 additions & 0 deletions app/resources/api/v2/delivery_configuration_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Api::V2::DeliveryConfigurationResource < ActiveResource::Base
self.element_name = "delivery_configuration"
self.site = Api::V2::FormDocumentResource.site
self.prefix = Api::V2::FormDocumentResource.prefix_source
self.include_format_in_path = false

belongs_to :form
end
1 change: 1 addition & 0 deletions app/resources/api/v2/form_document_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Api::V2::FormDocumentResource < ActiveResource::Base
self.include_format_in_path = false

has_many :steps, class_name: "Api::V2::StepResource"
has_many :delivery_configurations, class_name: "Api::V2::DeliveryConfigurationResource"

class << self
def find(form_id, tag, params: {})
Expand Down
12 changes: 9 additions & 3 deletions app/services/aws_ses_submission_service.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
class AwsSesSubmissionService
include SubmissionFilenameGenerator

def initialize(submission:)
def initialize(submission:, delivery:)
@submission = submission
@delivery = delivery
@journey = submission.journey
@form = submission.form
end
Expand All @@ -25,12 +26,17 @@ def submit
def deliver_submission_email
files = uploaded_files_in_answers

# This handles deliveries that were created before we started storing the formats on the delivery. This fallback and
# the deprecated `Form.submission_format` attribute can be removed from September 2026 when any failed deliveries
# will have been removed.
formats = @delivery.formats || @form.submission_format

csv_filename = nil
if @form.submission_format.include? "json"
if formats.include? "json"
json_filename = generate_json_filename
files.merge!({ json_filename => generate_json_submission })
end
if @form.submission_format.include? "csv"
if formats.include? "csv"
csv_filename = generate_csv_filename
files.merge!({ csv_filename => generate_csv_submission })
end
Expand Down
83 changes: 58 additions & 25 deletions app/services/form_submission_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ def submit
validate_submission
validate_confirmation_email_address if requested_confirmation?

submission = deliver_submission
submission = create_submission_record
enqueue_deliveries(submission)

LogEventService.log_submit(
current_context,
requested_email_confirmation: requested_confirmation?,
preview: mode.preview?,
)

enqueue_send_confirmation_email_job(submission:) if requested_confirmation? || send_copy_of_answers?

submission_reference
Expand Down Expand Up @@ -68,26 +76,32 @@ def validate_submission
raise StandardError, "Form id(#{form.id}) has no completed steps i.e questions/answers to submit" if current_context.completed_steps.blank?
end

def deliver_submission
submission =
case form.submission_type
when "s3"
enqueue_deliver_submission_job(SendS3SubmissionJob)
when "email"
enqueue_deliver_submission_job(SendSubmissionJob)
else
raise "unrecognized submission delivery method #{form.submission_type.inspect}"
end
def enqueue_deliveries(submission)
form.delivery_configurations
.filter { |c| c.delivery_schedule == "immediate" }
.each { |c| enqueue_delivery(c, submission) }
end

LogEventService.log_submit(
current_context,
requested_email_confirmation: requested_confirmation?,
preview: mode.preview?,
submission_type: form.submission_type,
submission_format: form.submission_format,
def enqueue_delivery(delivery_configuration, submission)
delivery = submission.deliveries.create!(
delivery_schedule: :immediate,
delivery_method: delivery_configuration.delivery_method,
formats: delivery_configuration.formats,
)

submission
job_class = resolve_submission_job_class(delivery_configuration)
enqueue_deliver_submission_job(job_class, submission, delivery)
end

def resolve_submission_job_class(delivery_configuration)
case delivery_configuration.delivery_method
when "s3"
SendS3SubmissionJob
when "email"
SendSubmissionJob
else
raise "unrecognized delivery method #{delivery_configuration.delivery_method.inspect}"
end
end

def create_submission_record
Expand All @@ -103,16 +117,35 @@ def create_submission_record
)
end

def enqueue_deliver_submission_job(job_class)
submission = create_submission_record
delivery = submission.deliveries.create!(delivery_schedule: :immediate)

def enqueue_deliver_submission_job(job_class, submission, delivery)
job_class.perform_later(delivery) do |job|
next if job.successfully_enqueued?

submission.destroy!
message_suffix = ": #{job.enqueue_error&.message}" if job.enqueue_error
raise StandardError, "Failed to enqueue submission for reference #{submission_reference}#{message_suffix}"
message_suffix = " Error: #{job.enqueue_error&.message}" if job.enqueue_error

# If the first or only delivery job fails to enqueue, delete the submission and raise an error so the user sees an
# error and can retry
if submission.deliveries.reload.one?
submission.destroy!
raise StandardError, "Failed to enqueue delivery for method #{delivery.delivery_method} for submission with reference #{submission_reference}. The submission was deleted, so the user can retry.#{message_suffix}"
else
delivery.update!(
failed_at: Time.zone.now,
failure_reason: "enqueue_failed",
)

# Don't raise an exception so we will attempt to queue delivery remaining delivery methods
message = "Failed to enqueue submission delivery. Some delivery methods were successfully enqueued, so this delivery needs to be re-attempted by running a rake task"
log_extra_attributes = {
delivery_id: delivery.id,
delivery_method: delivery.delivery_method,
enqueue_error: job.enqueue_error&.message,
}
Sentry.capture_message(message, extra: log_extra_attributes.merge({
submission_reference: submission_reference,
}))
Rails.logger.error(message, log_extra_attributes)
end
end

submission
Expand Down
6 changes: 3 additions & 3 deletions app/services/log_event_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ def self.log_form_start
EventLogger.log_form_event("visit")
end

def self.log_submit(context, requested_email_confirmation:, preview:, submission_type:, submission_format:)
def self.log_submit(context, requested_email_confirmation:, preview:)
if preview
EventLogger.log_form_event("preview_submission", { submission_type:, submission_format: })
EventLogger.log_form_event("preview_submission")
else
# Logging to Splunk
EventLogger.log_form_event("submission", { submission_type:, submission_format: })
EventLogger.log_form_event("submission")

EventLogger.log_form_event("requested_email_confirmation") if requested_email_confirmation

Expand Down
12 changes: 9 additions & 3 deletions app/services/s3_submission_service.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class S3SubmissionService
def initialize(submission:)
def initialize(submission:, delivery:)
@submission = submission
@delivery = delivery
@journey = submission.journey
@form = submission.form
@file_upload_bucket_name = Settings.aws.file_upload_s3_bucket_name
Expand All @@ -15,14 +16,19 @@ def submit
# file arrives and the referenced files will already be present
copy_uploaded_files_to_bucket

# This handles deliveries that were created before we started storing the formats on the delivery. This fallback and
# the deprecated `Form.submission_format` attribute can be removed from September 2026 when any failed deliveries
# will have been removed.
formats = @delivery.formats || @form.submission_format

submission_content, key =
case @form.submission_format
case formats
when %w[csv]
[generate_csv_submission, generate_key("form_submission.csv")]
when %w[json]
[generate_json_submission, generate_key("form_submission.json")]
else
raise StandardError, "Unsupported submission format: #{@form.submission_format.inspect}"
raise StandardError, "Unsupported submission format: #{formats.inspect}"
end

upload_submission_to_s3(submission_content, key)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AddDeliveryMethodAndFormatsToDeliveries < ActiveRecord::Migration[8.1]
def change
change_table :deliveries, bulk: true do |t|
t.string :delivery_method, null: true
t.string :formats, array: true, null: true
end
end
end
Loading