forked from zammad/zammad
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjob.rb
178 lines (123 loc) · 3.94 KB
/
job.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
class Job < ApplicationModel
include ChecksClientNotification
include ChecksConditionValidation
include ChecksHtmlSanitized
include HasTimeplan
include Job::Assets
OBJECTS_BATCH_SIZE = 2_000
store :condition
store :perform
validates :name, presence: true, uniqueness: { case_sensitive: false }
validates :object, presence: true, inclusion: { in: %w[Ticket User Organization] }
validates :perform, 'validations/verify_perform_rules': true
before_save :updated_matching, :update_next_run_at
validates :note, length: { maximum: 250 }
sanitized_html :note
=begin
verify each job if needed to run (e. g. if true and times are matching) and execute it
Job.run
=end
def self.run
start_at = Time.zone.now
Job.where(active: true).each do |job|
next if !job.executable?
job.run(false, start_at)
end
true
end
=begin
execute a single job if needed (e. g. if true and times are matching)
job = Job.find(123)
job.run
force to run job (ignore times are matching)
job.run(true)
=end
def run(force = false, start_at = Time.zone.now)
logger.debug { "Execute job #{inspect}" }
object_ids = start_job(start_at, force)
return if object_ids.nil?
object_ids.each_slice(10) do |slice|
run_slice(slice)
end
finish_job
end
def executable?(start_at = Time.zone.now)
return false if !active
# only execute jobs older than 1 min to give admin time to make last-minute changes
return false if updated_at > 1.minute.ago
# check if job got stuck
return false if running == true && last_run_at && 1.day.ago < last_run_at
# check if jobs need to be executed
# ignore if job was running within last 10 min.
return false if last_run_at && last_run_at > start_at - 10.minutes
true
end
def matching_count
object_count, _objects = object.constantize.selectors(condition, limit: 1, execution_time: true)
object_count || 0
end
private
def next_run_at_calculate(time = Time.zone.now)
return nil if !active
if last_run_at && (time - last_run_at).positive?
time += 10.minutes
end
timeplan_calculation.next_at(time)
end
def updated_matching
self.matching = matching_count
end
def update_next_run_at
self.next_run_at = next_run_at_calculate
end
def finish_job
Transaction.execute(reset_user_id: true) do
mark_as_finished
end
end
def mark_as_finished
self.running = false
self.last_run_at = Time.zone.now
save!
end
def start_job(start_at, force)
Transaction.execute(reset_user_id: true) do
next if !start_job_executable?(start_at, force)
next if !start_job_in_timeplan?(start_at, force)
object_count, objects = object.constantize.selectors(condition, limit: OBJECTS_BATCH_SIZE, execution_time: true)
logger.debug { "Job #{name} with #{object_count} object(s)" }
mark_as_started(objects&.count || 0)
objects&.pluck(:id) || []
end
end
def start_job_executable?(start_at, force)
return true if executable?(start_at) || force
if next_run_at && next_run_at <= Time.zone.now
save!
end
false
end
def start_job_in_timeplan?(start_at, force)
return true if in_timeplan?(start_at) || force
save! # trigger updating matching tickets count and next_run_at time even if not in timeplan
false
end
def mark_as_started(batch_count)
self.processed = batch_count
self.running = true
self.last_run_at = Time.zone.now
save!
end
def run_slice(slice)
Transaction.execute(disable_notification: disable_notification, reset_user_id: true) do
_, objects = object.constantize.selectors(condition, limit: OBJECTS_BATCH_SIZE, execution_time: true)
return if objects.nil?
objects
.where(id: slice)
.each do |object|
object.perform_changes(self, 'job')
end
end
end
end