Skip to content

Commit fd8ecda

Browse files
committed
[TAN-7137] Rework job into a sweep and move materialization to controllers
The ProcessScheduledPublicationTransitionsJob (plural) now sweeps through all due transitions by default, but you can also target a specific one by passing an admin_publication. Moved synchronous materialization out of SideFx services and into controllers, right before assign_attributes. This sidesteps conflicts from unpersisted changes and avoids infinite recursion. Also added the job to hourly_jobs.rake as a safety net.
1 parent bbb2c06 commit fd8ecda

9 files changed

Lines changed: 66 additions & 85 deletions

File tree

back/app/controllers/web_api/v1/folders_controller.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def create
9393
end
9494

9595
def update
96+
process_due_transition(@project_folder)
9697
@project_folder.assign_attributes project_folder_params
9798
authorize @project_folder
9899
remove_image_if_requested!(@project_folder, project_folder_params, :header_bg)
@@ -113,7 +114,9 @@ def destroy
113114
frozen_folder = nil
114115
frozen_projects = nil
115116

117+
process_due_transition(@project_folder)
116118
@project_folder.projects.each do |project|
119+
process_due_transition(project)
117120
SideFxProjectService.new.before_destroy(project, current_user)
118121
end
119122

@@ -135,6 +138,13 @@ def destroy
135138

136139
private
137140

141+
def process_due_transition(publication)
142+
admin_pub = publication.admin_publication
143+
return unless admin_pub.scheduled_at&.<=(Time.current)
144+
145+
ProcessScheduledPublicationTransitionsJob.new.run(admin_pub)
146+
end
147+
138148
def set_project_folder
139149
@project_folder = ProjectFolders::Folder.find(params[:id])
140150
authorize @project_folder

back/app/controllers/web_api/v1/projects_controller.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ def update
276276
params[:project][:area_ids] ||= [] if params[:project].key?(:area_ids)
277277
params[:project][:global_topic_ids] ||= [] if params[:project].key?(:global_topic_ids)
278278

279+
process_due_transition(@project)
280+
279281
project_params = permitted_attributes(@project)
280282

281283
@project.assign_attributes project_params
@@ -296,6 +298,7 @@ def update
296298
end
297299

298300
def destroy
301+
process_due_transition(@project)
299302
sidefx.before_destroy(@project, current_user)
300303
if @project.destroy
301304
sidefx.after_destroy(@project, current_user)
@@ -420,6 +423,13 @@ def set_project
420423
authorize @project
421424
end
422425

426+
def process_due_transition(publication)
427+
admin_pub = publication.admin_publication
428+
return unless admin_pub.scheduled_at&.<=(Time.current)
429+
430+
ProcessScheduledPublicationTransitionsJob.new.run(admin_pub)
431+
end
432+
423433
def base_render_mini_index
424434
render json: linked_json(
425435
@projects,

back/app/jobs/process_scheduled_publication_transition_job.rb renamed to back/app/jobs/process_scheduled_publication_transitions_job.rb

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
# frozen_string_literal: true
22

3-
class ProcessScheduledPublicationTransitionJob < ApplicationJob
3+
class ProcessScheduledPublicationTransitionsJob < ApplicationJob
44
queue_as :default
55

6-
def run(admin_publication_id)
7-
admin_pub = AdminPublication.find_by(id: admin_publication_id)
8-
return unless admin_pub
9-
# We check if the transition is due twice. The important check happens inside the lock.
10-
# This one is just an optimization to avoid locking unnecessarily.
11-
return unless transition_due?(admin_pub)
6+
def run(admin_publication = nil)
7+
if admin_publication
8+
process(admin_publication)
9+
else
10+
AdminPublication
11+
.where(scheduled_at: ..Time.current)
12+
.find_each { |admin_pub| process(admin_pub) }
13+
end
14+
end
15+
16+
private
1217

18+
def process(admin_pub)
1319
admin_pub.with_lock do
14-
# Skip if the schedule was canceled, rescheduled to the future, or already processed.
15-
next unless transition_due?(admin_pub)
20+
return unless admin_pub.scheduled_at&.<=(Time.current)
1621

1722
target = admin_pub.scheduled_status
1823
current = admin_pub.read_attribute(:publication_status)
19-
next admin_pub.cancel_scheduled_transition! if current == target
24+
return admin_pub.cancel_scheduled_transition! if current == target
2025

2126
user = admin_pub.scheduled_by
2227

@@ -37,12 +42,6 @@ def run(admin_publication_id)
3742
end
3843
end
3944

40-
private
41-
42-
def transition_due?(admin_pub)
43-
admin_pub.scheduled_at.present? && admin_pub.scheduled_at <= Time.current
44-
end
45-
4645
def sidefx_service(publication)
4746
case publication
4847
when Project then SideFxProjectService.new

back/app/services/project_folders/side_fx_project_folder_service.rb

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ def after_create(folder, user)
1515
end
1616

1717
def before_update(folder, user)
18-
process_due_transition(folder.admin_publication)
1918
set_scheduled_by(folder.admin_publication, user)
2019
end
2120

@@ -58,12 +57,6 @@ def after_destroy(frozen_folder, user)
5857

5958
private
6059

61-
def process_due_transition(admin_pub)
62-
return unless admin_pub.scheduled_at&.<=(Time.current)
63-
64-
ProcessScheduledPublicationTransitionJob.new.run(admin_pub.id)
65-
end
66-
6760
def set_scheduled_by(admin_pub, user)
6861
return unless admin_pub.will_save_change_to_scheduled_status?
6962

@@ -74,9 +67,9 @@ def enqueue_scheduled_transition(admin_pub)
7467
return unless admin_pub.saved_change_to_scheduled_at?
7568
return unless admin_pub.scheduled_at.present?
7669

77-
ProcessScheduledPublicationTransitionJob
70+
ProcessScheduledPublicationTransitionsJob
7871
.set(wait_until: admin_pub.scheduled_at)
79-
.perform_later(admin_pub.id)
72+
.perform_later
8073
end
8174
end
8275
end

back/app/services/side_fx_project_service.rb

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def after_destroy_participation_data(project, user)
4949
end
5050

5151
def before_update(project, user)
52-
process_due_transition(project.admin_publication)
5352
@publication_status_was = project.admin_publication.publication_status_was
5453
@folder_id_was = project.admin_publication.parent_id_was
5554
set_scheduled_by(project.admin_publication, user)
@@ -79,9 +78,7 @@ def after_update(project, user)
7978
enqueue_scheduled_transition(project.admin_publication)
8079
end
8180

82-
def before_destroy(project, _user)
83-
process_due_transition(project.admin_publication)
84-
end
81+
def before_destroy(project, user); end
8582

8683
def after_destroy(frozen_project, user)
8784
ContentBuilder::LayoutService.new.clean_homepage_layout_when_publication_deleted(frozen_project)
@@ -133,12 +130,6 @@ def after_publish(project, user)
133130
LogActivityJob.perform_later project, 'published', user, project.updated_at.to_i
134131
end
135132

136-
def process_due_transition(admin_pub)
137-
return unless admin_pub.scheduled_at&.<=(Time.current)
138-
139-
ProcessScheduledPublicationTransitionJob.new.run(admin_pub.id)
140-
end
141-
142133
def set_scheduled_by(admin_pub, user)
143134
return unless admin_pub.will_save_change_to_scheduled_status?
144135

@@ -149,9 +140,9 @@ def enqueue_scheduled_transition(admin_pub)
149140
return unless admin_pub.saved_change_to_scheduled_at?
150141
return unless admin_pub.scheduled_at.present?
151142

152-
ProcessScheduledPublicationTransitionJob
143+
ProcessScheduledPublicationTransitionsJob
153144
.set(wait_until: admin_pub.scheduled_at)
154-
.perform_later(admin_pub.id)
145+
.perform_later
155146
end
156147

157148
def after_folder_changed(project, current_user)

back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace :cl2back do
77
Tenant.creation_finalized.each do |tenant|
88
Apartment::Tenant.switch(tenant.schema_name) do
99
AutomatedTransitionJob.perform_later
10+
ProcessScheduledPublicationTransitionsJob.perform_later
1011
CreatePeriodicActivitiesJob.perform_later now.to_i
1112
CreateHeatmapGenerationJob.perform_later now.to_i
1213
IdeaFeed::TopicModelingSchedulerJob.perform_later

back/spec/jobs/process_scheduled_publication_transition_job_spec.rb renamed to back/spec/jobs/process_scheduled_publication_transitions_job_spec.rb

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
require 'rails_helper'
44

5-
RSpec.describe ProcessScheduledPublicationTransitionJob do
5+
RSpec.describe ProcessScheduledPublicationTransitionsJob do
66
subject(:job) { described_class.new }
77

88
let(:user) { create(:admin) }
@@ -18,7 +18,7 @@
1818
end
1919

2020
it 'materializes the transition and clears scheduled fields' do
21-
job.run(admin_publication.id)
21+
job.run
2222
admin_publication.reload
2323

2424
expect(admin_publication.read_attribute(:publication_status)).to eq('published')
@@ -29,14 +29,14 @@
2929

3030
it 'sets first_published_at when publishing for the first time' do
3131
admin_publication.update_columns(first_published_at: nil)
32-
job.run(admin_publication.id)
32+
job.run
3333
admin_publication.reload
3434

3535
expect(admin_publication.first_published_at).to be_present
3636
end
3737

3838
it 'fires side effects via SideFxProjectService' do
39-
expect { job.run(admin_publication.id) }
39+
expect { job.run }
4040
.to have_enqueued_job(LogActivityJob)
4141
.with(project, 'changed', user, anything, anything)
4242
end
@@ -53,7 +53,7 @@
5353
end
5454

5555
it 'fires the published activity' do
56-
expect { job.run(admin_publication.id) }
56+
expect { job.run }
5757
.to have_enqueued_job(LogActivityJob)
5858
.with(project, 'published', user, anything, anything)
5959
end
@@ -69,7 +69,7 @@
6969
end
7070

7171
it 'does not fire the published activity' do
72-
job.run(admin_publication.id)
72+
job.run
7373
expect(LogActivityJob).not_to have_been_enqueued.with(project, 'published', anything, anything)
7474
end
7575
end
@@ -80,7 +80,7 @@
8080
end
8181

8282
it 'does nothing' do
83-
job.run(admin_publication.id)
83+
job.run
8484
admin_publication.reload
8585

8686
expect(admin_publication.scheduled_status).to eq('published')
@@ -98,7 +98,7 @@
9898
it 'clears scheduled fields without firing additional side effects' do
9999
ActiveJob::Base.queue_adapter.enqueued_jobs.clear
100100

101-
job.run(admin_publication.id)
101+
job.run
102102
admin_publication.reload
103103

104104
expect(admin_publication.scheduled_status).to be_nil
@@ -107,10 +107,19 @@
107107
end
108108
end
109109

110-
context 'missing record' do
111-
it 'exits gracefully' do
112-
expect { job.run('nonexistent-id') }.not_to raise_error
113-
end
110+
it 'processes multiple due transitions' do
111+
project2 = create(:project, admin_publication_attributes: { publication_status: 'draft' })
112+
admin_publication.update_columns(
113+
scheduled_status: 'archived', scheduled_at: 1.hour.ago, scheduled_by_id: user.id
114+
)
115+
project2.admin_publication.update_columns(
116+
scheduled_status: 'archived', scheduled_at: 30.minutes.ago, scheduled_by_id: user.id
117+
)
118+
119+
job.run
120+
121+
expect(admin_publication.reload.read_attribute(:publication_status)).to eq('archived')
122+
expect(project2.admin_publication.reload.read_attribute(:publication_status)).to eq('archived')
114123
end
115124
end
116125
end

back/spec/services/project_folders/side_fx_project_folder_service_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,14 @@
5050
project_folder.save!
5151

5252
expect { service.after_update(project_folder, user) }
53-
.to have_enqueued_job(ProcessScheduledPublicationTransitionJob)
54-
.with(project_folder.admin_publication.id)
53+
.to have_enqueued_job(ProcessScheduledPublicationTransitionsJob)
5554
end
5655

5756
it 'does not enqueue the transition job when no schedule is set' do
5857
project_folder.update!(title_multiloc: { en: 'changed' })
5958

6059
expect { service.after_update(project_folder, user) }
61-
.not_to have_enqueued_job(ProcessScheduledPublicationTransitionJob)
60+
.not_to have_enqueued_job(ProcessScheduledPublicationTransitionsJob)
6261
end
6362
end
6463

back/spec/services/side_fx_project_service_spec.rb

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@
102102
project.save!
103103

104104
expect { service.after_update(project, user) }
105-
.to have_enqueued_job(ProcessScheduledPublicationTransitionJob)
106-
.with(project.admin_publication.id)
105+
.to have_enqueued_job(ProcessScheduledPublicationTransitionsJob)
107106
end
108107

109108
it 'does not enqueue the transition job when no schedule is set' do
@@ -112,39 +111,9 @@
112111
project.save!
113112

114113
expect { service.after_update(project, user) }
115-
.not_to have_enqueued_job(ProcessScheduledPublicationTransitionJob)
114+
.not_to have_enqueued_job(ProcessScheduledPublicationTransitionsJob)
116115
end
117116

118-
it 'materializes a due transition before the update without infinite loop' do
119-
project.admin_publication.update_columns(
120-
publication_status: 'draft', scheduled_status: 'published',
121-
scheduled_at: 1.hour.ago, scheduled_by_id: user.id
122-
)
123-
project.admin_publication.reload
124-
project.assign_attributes(title_multiloc: { en: 'changed' })
125-
126-
service.before_update(project, user)
127-
128-
project.admin_publication.reload
129-
expect(project.admin_publication.read_attribute(:publication_status)).to eq('published')
130-
expect(project.admin_publication.scheduled_status).to be_nil
131-
end
132-
end
133-
134-
describe 'before_destroy' do
135-
it 'materializes a due transition before destroy' do
136-
project.admin_publication.update_columns(
137-
publication_status: 'draft', scheduled_status: 'published',
138-
scheduled_at: 1.hour.ago, scheduled_by_id: user.id
139-
)
140-
project.admin_publication.reload
141-
142-
service.before_destroy(project, user)
143-
144-
project.admin_publication.reload
145-
expect(project.admin_publication.read_attribute(:publication_status)).to eq('published')
146-
expect(project.admin_publication.first_published_at).to be_present
147-
end
148117
end
149118

150119
describe 'after_destroy' do

0 commit comments

Comments
 (0)