From 753af9d5898d0d4a042bad76a5d380bbfbd3ebcb Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 17 Nov 2017 11:39:57 +0100 Subject: [PATCH] Small improvements for jobs model and execution of jobs. --- app/models/job.rb | 105 ++++++++++----- test/unit/job_test.rb | 303 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 364 insertions(+), 44 deletions(-) diff --git a/app/models/job.rb b/app/models/job.rb index 03f06eecf..9d5b422a1 100644 --- a/app/models/job.rb +++ b/app/models/job.rb @@ -15,46 +15,83 @@ class Job < ApplicationModel before_create :updated_matching, :update_next_run_at before_update :updated_matching, :update_next_run_at +=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 jobs = Job.where(active: true, running: false) jobs.each do |job| - logger.debug "Execute job #{job.inspect}" - - next if !job.executable? - - matching = job.matching_count - if job.matching != matching - job.matching = matching - job.save - end - - next if !job.in_timeplan? - - # find tickets to change - ticket_count, tickets = Ticket.selectors(job.condition, 2_000) - - logger.debug "Job #{job.name} with #{ticket_count} tickets" - - job.processed = ticket_count || 0 - job.running = true - job.save - - if tickets - tickets.each do |ticket| - Transaction.execute(disable_notification: job.disable_notification, reset_user_id: true) do - ticket.perform_changes(job.perform, 'job') - end - end - end - - job.running = false - job.last_run_at = Time.zone.now - job.save + job.run(false, start_at) end true end - def executable? +=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}" + + if !executable?(start_at) && force == false + if next_run_at && next_run_at <= Time.zone.now + save! + end + return + end + + matching = matching_count + if self.matching != matching + self.matching = matching + save! + end + + if !in_timeplan?(start_at) && force == false + if next_run_at && next_run_at <= Time.zone.now + save! + end + return + end + + # find tickets to change + ticket_count, tickets = Ticket.selectors(condition, 2_000) + + logger.debug "Job #{name} with #{ticket_count} tickets" + + self.processed = ticket_count || 0 + self.running = true + save! + + if tickets + tickets.each do |ticket| + Transaction.execute(disable_notification: disable_notification, reset_user_id: true) do + ticket.perform_changes(perform, 'job') + end + end + end + + self.running = false + self.last_run_at = Time.zone.now + save! + end + + def executable?(start_at = Time.zone.now) return false if !active # only execute jobs, older then 1 min, to give admin posibility to change @@ -62,7 +99,7 @@ class Job < ApplicationModel # 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 > Time.zone.now - 10.minutes + return false if last_run_at && last_run_at > start_at - 10.minutes true end diff --git a/test/unit/job_test.rb b/test/unit/job_test.rb index 7e41e925d..d92edfe59 100644 --- a/test/unit/job_test.rb +++ b/test/unit/job_test.rb @@ -11,7 +11,7 @@ class JobTest < ActiveSupport::TestCase updated_by_id: 1, created_by_id: 1, ) - ticket1 = Ticket.create( + ticket1 = Ticket.create!( title: 'job test 1', group: group1, customer_id: 2, @@ -22,7 +22,7 @@ class JobTest < ActiveSupport::TestCase created_by_id: 1, updated_by_id: 1, ) - ticket2 = Ticket.create( + ticket2 = Ticket.create!( title: 'job test 2', group: group1, customer_id: 2, @@ -33,7 +33,7 @@ class JobTest < ActiveSupport::TestCase updated_at: Time.zone.now - 1.day, updated_by_id: 1, ) - ticket3 = Ticket.create( + ticket3 = Ticket.create!( title: 'job test 3', group: group2, customer_id: 2, @@ -44,7 +44,7 @@ class JobTest < ActiveSupport::TestCase updated_at: Time.zone.now - 1.day, updated_by_id: 1, ) - ticket4 = Ticket.create( + ticket4 = Ticket.create!( title: 'job test 4', group: group2, customer_id: 2, @@ -55,7 +55,7 @@ class JobTest < ActiveSupport::TestCase updated_at: Time.zone.now - 3.days, updated_by_id: 1, ) - ticket5 = Ticket.create( + ticket5 = Ticket.create!( title: 'job test 5', group: group2, customer_id: 2, @@ -249,7 +249,7 @@ class JobTest < ActiveSupport::TestCase end - test 'case 2' do + test 'with invalid state_id' do # create ticket group1 = Group.lookup(name: 'Users') @@ -258,7 +258,7 @@ class JobTest < ActiveSupport::TestCase updated_by_id: 1, created_by_id: 1, ) - ticket1 = Ticket.create( + ticket1 = Ticket.create!( title: 'job test 1', group: group1, customer_id: 2, @@ -269,7 +269,7 @@ class JobTest < ActiveSupport::TestCase created_by_id: 1, updated_by_id: 1, ) - ticket2 = Ticket.create( + ticket2 = Ticket.create!( title: 'job test 2', group: group1, customer_id: 2, @@ -575,9 +575,8 @@ class JobTest < ActiveSupport::TestCase end - test 'case 5' do + test 'check next_run_at' do - # create jobs job1 = Job.create_or_update( name: 'Test Job1', timeplan: { @@ -644,6 +643,290 @@ class JobTest < ActiveSupport::TestCase time_now = Time.zone.parse('2016-03-17 23:51:23 UTC') next_run_at = job1.next_run_at_calculate(time_now) assert_equal('2016-03-21 00:00:00 UTC', next_run_at.to_s) + end + + test 'update next run at' do + + travel_to Time.zone.local(2017, 11, 10, 22, 0o4, 44) + + job1 = Job.create_or_update( + name: 'Test Job1', + timeplan: { + days: { + Mon: false, + Tue: false, + Wed: false, + Thu: false, + Fri: false, + Sat: true, + Sun: false, + }, + hours: { + '0' => false, + '1' => false, + '2' => false, + '3' => false, + '4' => false, + '5' => false, + '6' => false, + '7' => false, + '8' => false, + '9' => false, + '10' => false, + '11' => false, + '12' => false, + '13' => false, + '14' => false, + '15' => false, + '16' => false, + '17' => false, + '18' => false, + '19' => false, + '20' => false, + '21' => false, + '22' => false, + '23' => true, + }, + minutes: { + '0' => true, + '10' => false, + '20' => false, + '30' => false, + '40' => false, + '50' => false, + }, + }, + condition: { + 'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] }, + }, + perform: { + 'ticket.action' => { 'value' => 'delete' }, + }, + disable_notification: true, + last_run_at: nil, + active: true, + created_by_id: 1, + created_at: Time.zone.now, + updated_by_id: 1, + updated_at: Time.zone.now, + ) + + assert_equal('2017-11-11 23:00:00 UTC', job1.next_run_at.to_s) + assert_not(job1.last_run_at) + + travel_to Time.zone.local(2017, 11, 16, 22, 0o4, 44) + + Job.run + + job1.reload + + assert_equal('2017-11-18 23:00:00 UTC', job1.next_run_at.to_s) + assert_not(job1.last_run_at) + + travel_back + + end + + test 'execute on certain time' do + + travel_to Time.zone.local(2017, 11, 16, 22, 0o4, 44) + + group1 = Group.lookup(name: 'Users') + ticket1 = Ticket.create!( + title: 'job test 1', + group: group1, + customer_id: 2, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + created_by_id: 1, + updated_by_id: 1, + ) + ticket2 = Ticket.create!( + title: 'job test 2', + group: group1, + customer_id: 2, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + created_by_id: 1, + updated_by_id: 1, + ) + + job1 = Job.create_or_update( + name: 'Test Job1', + timeplan: { + days: { + Mon: false, + Tue: false, + Wed: false, + Thu: true, + Fri: false, + Sat: false, + Sun: false, + }, + hours: { + '0' => false, + '1' => false, + '2' => false, + '3' => false, + '4' => false, + '5' => false, + '6' => false, + '7' => false, + '8' => false, + '9' => false, + '10' => false, + '11' => false, + '12' => false, + '13' => false, + '14' => false, + '15' => false, + '16' => false, + '17' => false, + '18' => false, + '19' => false, + '20' => false, + '21' => false, + '22' => false, + '23' => true, + }, + minutes: { + '0' => true, + '10' => false, + '20' => false, + '30' => false, + '40' => false, + '50' => false, + }, + }, + condition: { + 'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] }, + }, + perform: { + 'ticket.action' => { 'value' => 'delete' }, + }, + disable_notification: true, + last_run_at: nil, + active: true, + created_by_id: 1, + created_at: Time.zone.now, + updated_by_id: 1, + updated_at: Time.zone.now, + ) + Job.run + + assert(Ticket.find_by(id: ticket1.id)) + assert(Ticket.find_by(id: ticket2.id)) + + travel_to Time.zone.local(2017, 11, 16, 23, 0o4, 44) + + Job.run + + assert_not(Ticket.find_by(id: ticket1.id)) + assert_not(Ticket.find_by(id: ticket2.id)) + + travel_back + end + + test 'delete based on tag' do + + # create ticket + group1 = Group.lookup(name: 'Users') + group2 = Group.create_or_update( + name: 'JobTest2', + updated_by_id: 1, + created_by_id: 1, + ) + ticket1 = Ticket.create!( + title: 'job test 1', + group: group1, + customer_id: 2, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + created_at: Time.zone.now - 3.days, + updated_at: Time.zone.now - 3.days, + created_by_id: 1, + updated_by_id: 1, + ) + ticket1.tag_add('spam', 1) + ticket1.tag_add('test1 ', 1) + ticket2 = Ticket.create!( + title: 'job test 2', + group: group1, + customer_id: 2, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + created_at: Time.zone.now - 1.day, + created_by_id: 1, + updated_at: Time.zone.now - 1.day, + updated_by_id: 1, + ) + + job1 = Job.create_or_update( + name: 'Test Job1', + timeplan: { + days: { + Mon: true, + Tue: true, + Wed: true, + Thu: true, + Fri: true, + Sat: true, + Sun: true, + }, + hours: { + 0 => true, + 1 => true, + 2 => true, + 3 => true, + 4 => true, + 5 => true, + 6 => true, + 7 => true, + 8 => true, + 9 => true, + 10 => true, + 11 => true, + 12 => true, + 13 => true, + 14 => true, + 15 => true, + 16 => true, + 17 => true, + 18 => true, + 19 => true, + 20 => true, + 21 => true, + 22 => true, + 23 => true, + }, + minutes: { + 0 => true, + 10 => true, + 20 => true, + 30 => true, + 40 => true, + 50 => true, + }, + }, + condition: { + 'ticket.tags' => { 'operator' => 'contains one', 'value' => 'spam' }, + }, + perform: { + 'ticket.action' => { 'value' => 'delete' }, + }, + disable_notification: true, + last_run_at: nil, + updated_at: Time.zone.now - 15.minutes, + active: true, + updated_by_id: 1, + created_by_id: 1, + ) + assert(job1.executable?) + assert(job1.in_timeplan?) + Job.run + + assert_not(Ticket.find_by(id: ticket1.id)) + assert(Ticket.find_by(id: ticket2.id)) end