Skip to content

Commit d3d8550

Browse files
committed
Add job to schedule weekly deliveries
Add a job that will be run on a recurring schedule that finds all form_ids and modes to send submission batches for the previous week. For each form_id and mode, create a Delivery, associating the submissions with it, and schedule a SendSubmissionBatchJob to send the email.
1 parent fa3707f commit d3d8550

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class ScheduleWeeklyBatchDeliveriesJob < ApplicationJob
2+
# If we change the queue for this job, ensure we add a new alert in CloudWatch for failed executions
3+
queue_as :submissions
4+
5+
def perform
6+
CloudWatchService.record_job_started_metric(self.class.name)
7+
CurrentJobLoggingAttributes.job_class = self.class.name
8+
CurrentJobLoggingAttributes.job_id = job_id
9+
10+
batch_begin_at = 1.week.ago.in_time_zone(TimeZoneUtils.submission_time_zone).beginning_of_week(:monday)
11+
12+
BatchSubmissionsSelector.weekly_batches(batch_begin_at).each do |batch|
13+
existing_deliveries = batch.submissions.first.deliveries.weekly
14+
if existing_deliveries.any?
15+
Rails.logger.warn("Weekly batch delivery already exists for batch - skipping", {
16+
form_id: batch.form_id, mode: batch.mode, batch_begin_at:, delivery_id: existing_deliveries.first.id
17+
})
18+
next
19+
end
20+
21+
delivery = Delivery.create!(
22+
delivery_schedule: :weekly,
23+
submissions: batch.submissions,
24+
batch_begin_at:,
25+
)
26+
27+
send_batch_job = SendSubmissionBatchJob.perform_later(delivery:)
28+
29+
Rails.logger.info("Scheduled SendSubmissionBatchJob to send weekly submission batch", {
30+
form_id: batch.form_id,
31+
mode: batch.mode,
32+
batch_begin_date: batch_begin_at.to_date,
33+
send_submission_batch_job_id: send_batch_job.job_id,
34+
delivery_id: delivery.id,
35+
})
36+
end
37+
end
38+
end
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
require "rails_helper"
2+
3+
RSpec.describe ScheduleWeeklyBatchDeliveriesJob do
4+
include ActiveSupport::Testing::TimeHelpers
5+
include ActiveJob::TestHelper
6+
7+
let(:travel_time) { Time.utc(2025, 5, 20, 2, 0, 0) }
8+
let(:form_id) { 101 }
9+
let(:other_form_id) { 201 }
10+
let(:form_submissions) { create_list(:submission, 2, form_id: form_id, mode: "form") }
11+
let(:other_form_submissions) { create_list(:submission, 1, form_id: other_form_id, mode: "preview-draft") }
12+
let!(:batches) do
13+
[
14+
BatchSubmissionsSelector::Batch.new(101, "form", form_submissions),
15+
BatchSubmissionsSelector::Batch.new(201, "preview-draft", other_form_submissions),
16+
]
17+
end
18+
19+
around do |example|
20+
travel_to travel_time do
21+
example.run
22+
end
23+
end
24+
25+
before do
26+
allow(BatchSubmissionsSelector).to receive(:weekly_batches).and_return(batches.to_enum)
27+
end
28+
29+
context "when Deliveries do not already exist for batches" do
30+
before do
31+
described_class.perform_now
32+
end
33+
34+
it "calls the selector passing in the start time of the previous week" do
35+
expect(BatchSubmissionsSelector).to have_received(:weekly_batches).with(Time.utc(2025, 5, 11, 23, 0, 0))
36+
end
37+
38+
it "creates a delivery record per batch job" do
39+
expect(Delivery.weekly.count).to eq(2)
40+
expect(Delivery.first.submissions.map(&:id)).to match_array(form_submissions.map(&:id))
41+
expect(Delivery.second.submissions.map(&:id)).to match_array(other_form_submissions.map(&:id))
42+
end
43+
44+
it "enqueues a SendSubmissionBatchJob per batch" do
45+
expect(ActiveJob::Base.queue_adapter.enqueued_jobs.size).to eq(2)
46+
end
47+
48+
it "enqueues the jobs with the correct args" do
49+
enqueued_args = ActiveJob::Base.queue_adapter.enqueued_jobs.map { |j| j[:args].first }
50+
expect(enqueued_args.first).to include("delivery" => hash_including("_aj_globalid"))
51+
expect(locate_delivery(enqueued_args.first)).to eq(Delivery.first)
52+
53+
expect(enqueued_args.second).to include("delivery" => hash_including("_aj_globalid"))
54+
expect(locate_delivery(enqueued_args.second)).to eq(Delivery.second)
55+
end
56+
57+
describe "setting batch_begin_at" do
58+
context "when the week for the batch is the week the clocks go forwards" do
59+
let(:travel_time) { Time.utc(2025, 3, 31, 2, 0, 0) }
60+
61+
it "sets the batch_begin_at to the beginning of the week in GMT" do
62+
expect(Delivery.first.batch_begin_at).to eq(Time.utc(2025, 3, 24, 0, 0, 0))
63+
end
64+
end
65+
66+
context "when the week for the batch is the week after the clocks have gone forwards" do
67+
let(:travel_time) { Time.utc(2025, 4, 7, 2, 0, 0) }
68+
69+
it "sets the batch_begin_at to the beginning of the week in BST" do
70+
expect(Delivery.first.batch_begin_at).to eq(Time.utc(2025, 3, 30, 23, 0, 0))
71+
end
72+
end
73+
74+
context "when the week for the batch is the week the clocks go back" do
75+
let(:travel_time) { Time.zone.local(2025, 10, 27, 2, 0, 0) }
76+
77+
it "sets the batch_begin_at to the beginning of the week in BST" do
78+
expect(Delivery.first.batch_begin_at).to eq(Time.utc(2025, 10, 19, 23, 0, 0))
79+
end
80+
end
81+
82+
context "when the week for the batch is the week after the clocks have gone back" do
83+
let(:travel_time) { Time.utc(2025, 11, 3, 2, 0, 0) }
84+
85+
it "sets the batch_begin_at to the beginning of the week in GMT" do
86+
expect(Delivery.first.batch_begin_at).to eq(Time.utc(2025, 10, 27, 0, 0, 0))
87+
end
88+
end
89+
end
90+
end
91+
92+
context "when a Delivery already exists for a batch" do
93+
let!(:existing_delivery) { create(:delivery, delivery_schedule: :weekly, submissions: form_submissions) }
94+
95+
it "logs that the delivery will be skipped" do
96+
expect(Rails.logger).to receive(:warn).with(
97+
"Weekly batch delivery already exists for batch - skipping",
98+
hash_including(
99+
form_id: form_id,
100+
mode: "form",
101+
batch_begin_at: Time.utc(2025, 5, 11, 23, 0, 0),
102+
delivery_id: existing_delivery.id,
103+
),
104+
)
105+
106+
described_class.perform_now
107+
end
108+
109+
it "only creates a delivery for the batch without an existing delivery" do
110+
expect {
111+
described_class.perform_now
112+
}.to change(Delivery, :count).by(1)
113+
114+
expect(Delivery.last.submissions.map(&:id)).to match_array(other_form_submissions.map(&:id))
115+
end
116+
117+
it "only schedules a job for the batch without an existing delivery" do
118+
expect {
119+
described_class.perform_now
120+
}.to change { ActiveJob::Base.queue_adapter.enqueued_jobs.size }.by(1)
121+
122+
enqueued_args = ActiveJob::Base.queue_adapter.enqueued_jobs.map { |j| j[:args].first }
123+
expect(enqueued_args.first).to include("delivery" => hash_including("_aj_globalid"))
124+
expect(locate_delivery(enqueued_args.first)).to eq(Delivery.last)
125+
end
126+
end
127+
128+
def locate_delivery(enqueued_args)
129+
gid_string = enqueued_args.dig("delivery", "_aj_globalid")
130+
GlobalID::Locator.locate(gid_string)
131+
end
132+
end

0 commit comments

Comments
 (0)