2021-06-01 12:20:20 +00:00
|
|
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
2014-12-25 23:41:00 +00:00
|
|
|
|
|
|
|
class Job < ApplicationModel
|
2017-05-02 15:21:13 +00:00
|
|
|
include ChecksClientNotification
|
|
|
|
include ChecksConditionValidation
|
2021-04-12 09:49:26 +00:00
|
|
|
include ChecksHtmlSanitized
|
2019-01-29 14:04:47 +00:00
|
|
|
include ChecksPerformValidation
|
2017-01-31 17:13:45 +00:00
|
|
|
|
2016-03-20 19:09:52 +00:00
|
|
|
include Job::Assets
|
|
|
|
|
2014-12-25 23:41:00 +00:00
|
|
|
store :timeplan
|
|
|
|
store :condition
|
2016-03-18 02:04:49 +00:00
|
|
|
store :perform
|
2015-04-27 13:42:53 +00:00
|
|
|
validates :name, presence: true
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2021-08-19 12:29:28 +00:00
|
|
|
before_save :updated_matching, :update_next_run_at
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2021-04-12 09:49:26 +00:00
|
|
|
sanitized_html :note
|
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
verify each job if needed to run (e. g. if true and times are matching) and execute it
|
|
|
|
|
|
|
|
Job.run
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2014-12-25 23:41:00 +00:00
|
|
|
def self.run
|
2017-11-17 10:39:57 +00:00
|
|
|
start_at = Time.zone.now
|
2018-08-16 13:12:55 +00:00
|
|
|
jobs = Job.where(active: true)
|
2014-12-25 23:41:00 +00:00
|
|
|
jobs.each do |job|
|
2018-08-16 13:12:55 +00:00
|
|
|
next if !job.executable?
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
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
|
2016-03-18 02:04:49 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
force to run job (ignore times are matching)
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
job.run(true)
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def run(force = false, start_at = Time.zone.now)
|
2018-03-20 17:47:49 +00:00
|
|
|
logger.debug { "Execute job #{inspect}" }
|
2017-11-17 10:39:57 +00:00
|
|
|
|
2020-12-29 09:09:24 +00:00
|
|
|
ticket_ids = start_job(start_at, force)
|
2014-12-25 23:41:00 +00:00
|
|
|
|
2020-12-29 09:09:24 +00:00
|
|
|
return if ticket_ids.nil?
|
2019-07-26 14:31:24 +00:00
|
|
|
|
2020-12-29 09:09:24 +00:00
|
|
|
ticket_ids&.each_slice(10) do |slice|
|
|
|
|
run_slice(slice)
|
2014-12-25 23:41:00 +00:00
|
|
|
end
|
2017-11-17 10:39:57 +00:00
|
|
|
|
2020-12-29 09:09:24 +00:00
|
|
|
finish_job
|
2014-12-25 23:41:00 +00:00
|
|
|
end
|
|
|
|
|
2017-11-17 10:39:57 +00:00
|
|
|
def executable?(start_at = Time.zone.now)
|
2016-03-18 14:29:03 +00:00
|
|
|
return false if !active
|
2016-03-18 02:04:49 +00:00
|
|
|
|
2019-07-31 08:23:48 +00:00
|
|
|
# only execute jobs older than 1 min to give admin time to make last-minute changes
|
2016-03-18 02:04:49 +00:00
|
|
|
return false if updated_at > Time.zone.now - 1.minute
|
|
|
|
|
2018-08-16 13:12:55 +00:00
|
|
|
# check if job got stuck
|
|
|
|
return false if running == true && last_run_at && Time.zone.now - 1.day < last_run_at
|
|
|
|
|
2016-03-18 02:04:49 +00:00
|
|
|
# check if jobs need to be executed
|
|
|
|
# ignore if job was running within last 10 min.
|
2017-11-17 10:39:57 +00:00
|
|
|
return false if last_run_at && last_run_at > start_at - 10.minutes
|
2016-03-18 02:04:49 +00:00
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
def in_timeplan?(time = Time.zone.now)
|
2021-08-19 12:29:28 +00:00
|
|
|
Job::TimeplanCalculation.new(timeplan).contains?(time)
|
2016-03-18 02:04:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def matching_count
|
2020-02-20 12:34:16 +00:00
|
|
|
ticket_count, _tickets = Ticket.selectors(condition, limit: 1, execution_time: true)
|
2016-03-18 02:04:49 +00:00
|
|
|
ticket_count || 0
|
|
|
|
end
|
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
def next_run_at_calculate(time = Time.zone.now)
|
|
|
|
return nil if !active
|
|
|
|
|
2021-08-19 12:29:28 +00:00
|
|
|
if last_run_at && (time - last_run_at).positive?
|
|
|
|
time += 10.minutes
|
2016-03-18 14:29:03 +00:00
|
|
|
end
|
2021-08-19 12:29:28 +00:00
|
|
|
|
|
|
|
Job::TimeplanCalculation.new(timeplan).next_at(time)
|
2016-03-18 14:29:03 +00:00
|
|
|
end
|
|
|
|
|
2014-12-25 23:41:00 +00:00
|
|
|
private
|
|
|
|
|
|
|
|
def updated_matching
|
2016-03-18 02:04:49 +00:00
|
|
|
self.matching = matching_count
|
2014-12-25 23:41:00 +00:00
|
|
|
end
|
|
|
|
|
2016-03-18 14:29:03 +00:00
|
|
|
def update_next_run_at
|
|
|
|
self.next_run_at = next_run_at_calculate
|
|
|
|
end
|
|
|
|
|
2020-12-29 09:09:24 +00:00
|
|
|
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
|
|
|
|
if start_job_executable?(start_at, force) && start_job_ensure_matching_count && start_job_in_timeplan?(start_at, force)
|
|
|
|
ticket_count, tickets = Ticket.selectors(condition, limit: 2_000, execution_time: true)
|
|
|
|
|
|
|
|
logger.debug { "Job #{name} with #{ticket_count} tickets" }
|
|
|
|
|
|
|
|
mark_as_started(ticket_count)
|
|
|
|
|
|
|
|
tickets&.pluck(:id) || []
|
|
|
|
end
|
|
|
|
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_ensure_matching_count
|
|
|
|
matching = matching_count
|
|
|
|
|
|
|
|
if self.matching != matching
|
|
|
|
self.matching = matching
|
|
|
|
save!
|
|
|
|
end
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def start_job_in_timeplan?(start_at, force)
|
|
|
|
return true if in_timeplan?(start_at) || force
|
|
|
|
|
|
|
|
if next_run_at && next_run_at <= Time.zone.now
|
|
|
|
save!
|
|
|
|
end
|
2018-10-09 06:17:41 +00:00
|
|
|
|
2020-12-29 09:09:24 +00:00
|
|
|
false
|
2014-12-25 23:41:00 +00:00
|
|
|
end
|
2016-03-18 02:04:49 +00:00
|
|
|
|
2020-12-29 09:09:24 +00:00
|
|
|
def mark_as_started(ticket_count)
|
|
|
|
self.processed = ticket_count || 0
|
|
|
|
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
|
|
|
|
_, tickets = Ticket.selectors(condition, limit: 2_000, execution_time: true)
|
|
|
|
|
|
|
|
tickets
|
|
|
|
&.where(id: slice)
|
|
|
|
&.each do |ticket|
|
|
|
|
ticket.perform_changes(self, 'job')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-04-27 14:15:29 +00:00
|
|
|
end
|