marty 9.5.1 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/README.md +40 -31
- data/app/components/marty/background_job/schedule_jobs_dashboard.rb +2 -2
- data/app/components/marty/background_job/schedule_jobs_grid.rb +22 -5
- data/app/components/marty/background_job/schedule_jobs_logs.rb +12 -0
- data/app/components/marty/base_rule_view.rb +0 -4
- data/app/components/marty/delorean_rule_view.rb +11 -5
- data/app/components/marty/promise_view.rb +15 -0
- data/app/jobs/marty/cron_job.rb +30 -28
- data/app/jobs/marty/delorean_background_job.rb +8 -0
- data/app/models/marty/background_job/schedule.rb +24 -1
- data/app/models/marty/delorean_rule.rb +1 -1
- data/app/models/marty/vw_promise.rb +6 -4
- data/app/services/marty/background_job/fetch_missing_in_schedule_cron_jobs.rb +8 -6
- data/app/services/marty/background_job/update_schedule.rb +15 -8
- data/app/services/marty/jobs/schedule.rb +8 -4
- data/app/services/marty/promises/delorean/create.rb +2 -1
- data/app/services/marty/promises/ruby/create.rb +2 -1
- data/config/locales/en.yml +1 -0
- data/db/migrate/510_schedule_job_to_remove_old_promises.rb +4 -2
- data/db/migrate/523_add_timeout_to_promises.rb +5 -0
- data/db/migrate/524_add_timeout_to_promise_view.rb +45 -0
- data/db/migrate/525_add_arguments_to_jobs_schedules.rb +10 -0
- data/db/migrate/526_add_schedule_id_to_delayed_jobs.rb +14 -0
- data/lib/marty/delayed_job/queue_adapter.rb +38 -0
- data/lib/marty/delayed_job/scheduled_job_plugin.rb +9 -6
- data/lib/marty/diagnostic/environment_variables.rb +1 -1
- data/lib/marty/monkey.rb +11 -0
- data/lib/marty/promise_job.rb +2 -1
- data/lib/marty/promise_ruby_job.rb +2 -1
- data/lib/marty/version.rb +1 -1
- data/spec/dummy/.foreman +2 -0
- data/spec/dummy/Procfile +2 -0
- data/spec/dummy/app/jobs/test_failing_job.rb +5 -1
- data/spec/dummy/app/models/gemini/my_rule.rb +2 -0
- data/spec/dummy/app/models/gemini/xyz_rule.rb +2 -0
- data/spec/dummy/delorean/jobs.dl +6 -0
- data/spec/features/delayed_jobs_grid_spec.rb +3 -2
- data/spec/features/schedule_jobs_dashboard_spec.rb +12 -12
- data/spec/jobs/cron_job_spec.rb +16 -12
- data/spec/jobs/delorean_background_job_spec.rb +50 -0
- data/spec/models/promise_spec.rb +1 -0
- data/spec/services/background_job/fetch_missing_in_schedule_cron_jobs_spec.rb +5 -3
- data/spec/services/jobs/schedule_spec.rb +5 -4
- metadata +12 -2
@@ -8,20 +8,43 @@ module Marty
|
|
8
8
|
REGEX = %r{\A(((\*?[\d/,\-]*)\s){3,4}(\*?([\d/,\-])*\s)(\*?([\d/,\-])*))\z}i
|
9
9
|
|
10
10
|
validates :job_class, :cron, :state, presence: true
|
11
|
-
validates :job_class, uniqueness: true
|
12
11
|
validates :cron, format: { with: REGEX }
|
13
12
|
|
14
13
|
validate :job_class_validation
|
14
|
+
validate :arguments_array_validation
|
15
|
+
validate :job_class_uniqueness_validation
|
16
|
+
|
17
|
+
has_one :delayed_job, class_name: '::Delayed::Job', dependent: :destroy
|
15
18
|
|
16
19
|
ALL_STATES = %w[on off].freeze
|
17
20
|
enum state: ALL_STATES.zip(ALL_STATES).to_h
|
18
21
|
|
22
|
+
scope :by_arguments, lambda { |arguments|
|
23
|
+
where('arguments = ?', arguments.to_json)
|
24
|
+
}
|
25
|
+
|
19
26
|
def job_class_validation
|
20
27
|
job_class.constantize.respond_to?(:schedule)
|
21
28
|
rescue NameError
|
22
29
|
errors.add(:job_class, "doesn't exist")
|
23
30
|
false
|
24
31
|
end
|
32
|
+
|
33
|
+
def arguments_array_validation
|
34
|
+
return if arguments.is_a? Array
|
35
|
+
|
36
|
+
errors.add(:arguments, 'must be an Array')
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def job_class_uniqueness_validation
|
41
|
+
return unless self.class.by_arguments(arguments).
|
42
|
+
where.not(id: id).
|
43
|
+
where(job_class: job_class).any?
|
44
|
+
|
45
|
+
errors.add(:arguments, 'are not unique')
|
46
|
+
false
|
47
|
+
end
|
25
48
|
end
|
26
49
|
end
|
27
50
|
end
|
@@ -23,16 +23,18 @@ class Marty::VwPromise < Marty::Base
|
|
23
23
|
def user_id
|
24
24
|
0
|
25
25
|
end
|
26
|
-
alias_method :job_id, :user_id
|
27
26
|
|
28
|
-
def
|
29
|
-
|
27
|
+
def job_id
|
28
|
+
0
|
30
29
|
end
|
31
|
-
[:start_dt, :end_dt].each { |m| alias_method m, :result }
|
32
30
|
|
33
31
|
def status
|
34
32
|
true
|
35
33
|
end
|
34
|
+
|
35
|
+
[:result, :start_dt, :end_dt, :timeout].each do |m|
|
36
|
+
define_method(m) { nil }
|
37
|
+
end
|
36
38
|
end
|
37
39
|
|
38
40
|
def self.root
|
@@ -2,17 +2,19 @@ module Marty
|
|
2
2
|
module BackgroundJob
|
3
3
|
module FetchMissingInScheduleCronJobs
|
4
4
|
def self.call
|
5
|
-
in_dashboard = Marty::BackgroundJob::Schedule.
|
5
|
+
in_dashboard = Marty::BackgroundJob::Schedule.all
|
6
6
|
|
7
|
-
names_conditions = in_dashboard.map do |
|
8
|
-
"%job_class: #{
|
7
|
+
names_conditions = in_dashboard.map do |schedule|
|
8
|
+
"%job_class: #{schedule.job_class}\n%"
|
9
9
|
end
|
10
10
|
|
11
|
-
Delayed::Job.
|
11
|
+
djs = Delayed::Job.
|
12
12
|
where.not(cron: nil).
|
13
13
|
where.not(cron: '').
|
14
|
-
where("handler ILIKE '%job_class:%'")
|
15
|
-
|
14
|
+
where("handler ILIKE '%job_class:%'")
|
15
|
+
|
16
|
+
djs.where.not('handler ILIKE ANY ( array[?] )', names_conditions).
|
17
|
+
or(djs.where.not(schedule_id: in_dashboard.map(&:id)))
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
@@ -4,26 +4,33 @@ module Marty
|
|
4
4
|
def self.call(id:, job_class:)
|
5
5
|
model = Marty::BackgroundJob::Schedule.find_by(id: id)
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
if model.blank? || model.off?
|
8
|
+
return remove_schedule(
|
9
|
+
schedule_id: id,
|
10
|
+
job_class: job_class
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
return schedule(schedule_obj: model) if model.on?
|
10
15
|
end
|
11
16
|
|
12
|
-
def self.remove_schedule(job_class:)
|
17
|
+
def self.remove_schedule(schedule_id:, job_class:)
|
13
18
|
klass = job_class.constantize
|
14
|
-
|
19
|
+
return true unless klass.respond_to?(:remove_schedule)
|
20
|
+
|
21
|
+
klass.remove_schedule(Delayed::Job.find_by(schedule_id: schedule_id))
|
15
22
|
|
16
23
|
true
|
17
24
|
rescue NameError
|
18
25
|
false
|
19
26
|
end
|
20
27
|
|
21
|
-
def self.schedule(
|
22
|
-
klass = job_class.constantize
|
28
|
+
def self.schedule(schedule_obj:)
|
29
|
+
klass = schedule_obj.job_class.constantize
|
23
30
|
|
24
31
|
return false unless klass.respond_to?(:schedule)
|
25
32
|
|
26
|
-
klass.schedule
|
33
|
+
klass.schedule(schedule_obj: schedule_obj)
|
27
34
|
|
28
35
|
true
|
29
36
|
rescue NameError
|
@@ -3,7 +3,7 @@ module Marty
|
|
3
3
|
module Schedule
|
4
4
|
extend Delorean::Functions
|
5
5
|
|
6
|
-
delorean_fn :call
|
6
|
+
delorean_fn :call do
|
7
7
|
glob = Rails.root.join('app/jobs/**/*_job.rb')
|
8
8
|
Dir.glob(glob).sort.each { |f| require f }
|
9
9
|
|
@@ -12,9 +12,13 @@ module Marty
|
|
12
12
|
|
13
13
|
Delayed::Job.where.not(cron: nil).each(&:destroy!)
|
14
14
|
|
15
|
-
Marty::
|
16
|
-
|
17
|
-
|
15
|
+
Marty::BackgroundJob::Schedule.all.map do |schedule|
|
16
|
+
Marty::BackgroundJob::UpdateSchedule.call(
|
17
|
+
id: schedule.id,
|
18
|
+
job_class: schedule.job_class,
|
19
|
+
)
|
20
|
+
|
21
|
+
[schedule.job_class, schedule.arguments, schedule.cron]
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
data/config/locales/en.yml
CHANGED
@@ -2,13 +2,15 @@ class ScheduleJobToRemoveOldPromises < ActiveRecord::Migration[4.2]
|
|
2
2
|
def up
|
3
3
|
cron_every_hour = '0 * * * *'
|
4
4
|
|
5
|
-
Marty::BackgroundJob::Schedule.
|
5
|
+
schedule = Marty::BackgroundJob::Schedule.new(
|
6
6
|
job_class: 'Marty::RemoveOldPromisesJob',
|
7
7
|
cron: cron_every_hour,
|
8
8
|
state: 'on'
|
9
9
|
)
|
10
10
|
|
11
|
-
|
11
|
+
# Since we add `arguments` column to the model in later migrations,
|
12
|
+
# we should skip it's validation here
|
13
|
+
schedule.save!(validate: false)
|
12
14
|
end
|
13
15
|
|
14
16
|
def down
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class AddTimeoutToPromiseView < ActiveRecord::Migration[4.2]
|
2
|
+
def up
|
3
|
+
execute <<~SQL
|
4
|
+
DROP VIEW IF EXISTS marty_vw_promises;
|
5
|
+
CREATE OR REPLACE VIEW marty_vw_promises
|
6
|
+
AS
|
7
|
+
SELECT
|
8
|
+
id,
|
9
|
+
title,
|
10
|
+
user_id,
|
11
|
+
cformat,
|
12
|
+
parent_id,
|
13
|
+
job_id,
|
14
|
+
status,
|
15
|
+
start_dt,
|
16
|
+
end_dt,
|
17
|
+
priority,
|
18
|
+
timeout
|
19
|
+
FROM marty_promises;
|
20
|
+
|
21
|
+
GRANT SELECT ON marty_vw_promises TO public;
|
22
|
+
SQL
|
23
|
+
end
|
24
|
+
|
25
|
+
def down
|
26
|
+
execute <<~SQL
|
27
|
+
DROP VIEW IF EXISTS marty_vw_promises;
|
28
|
+
CREATE OR REPLACE VIEW marty_vw_promises
|
29
|
+
AS
|
30
|
+
SELECT
|
31
|
+
id,
|
32
|
+
title,
|
33
|
+
user_id,
|
34
|
+
cformat,
|
35
|
+
parent_id,
|
36
|
+
job_id,
|
37
|
+
status,
|
38
|
+
start_dt,
|
39
|
+
end_dt
|
40
|
+
FROM marty_promises;
|
41
|
+
|
42
|
+
GRANT SELECT ON marty_vw_promises TO public;
|
43
|
+
SQL
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class AddArgumentsToJobsSchedules < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
add_column :marty_background_job_schedules, :arguments, :jsonb, default: [], null: false
|
4
|
+
|
5
|
+
remove_index :marty_background_job_schedules, :job_class
|
6
|
+
add_index :marty_background_job_schedules, [:job_class, :arguments], unique: true
|
7
|
+
|
8
|
+
add_column :marty_background_job_logs, :arguments, :jsonb, default: [], null: false
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class AddScheduleIdToDelayedJobs < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
add_column :delayed_jobs, :schedule_id, :integer
|
4
|
+
add_index :delayed_jobs, :schedule_id
|
5
|
+
|
6
|
+
reversible do |dir|
|
7
|
+
dir.up { set_ids }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_ids
|
12
|
+
::Marty::Jobs::Schedule.call
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copied from https://github.com/codez/delayed_cron_job/blob/master/lib/delayed_cron_job/active_job/queue_adapter.rb
|
2
|
+
# Only schedule_id is added
|
3
|
+
module Marty
|
4
|
+
module DelayedJob
|
5
|
+
module QueueAdapter
|
6
|
+
def self.included(klass)
|
7
|
+
klass.send(:alias_method, :enqueue, :enqueue_with_cron)
|
8
|
+
klass.send(:alias_method, :enqueue_at, :enqueue_at_with_cron)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.extended(klass)
|
12
|
+
meta = class << klass; self; end
|
13
|
+
meta.send(:alias_method, :enqueue, :enqueue_with_cron)
|
14
|
+
meta.send(:alias_method, :enqueue_at, :enqueue_at_with_cron)
|
15
|
+
end
|
16
|
+
|
17
|
+
def enqueue_with_cron(job)
|
18
|
+
enqueue_at(job, nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
def enqueue_at_with_cron(job, timestamp)
|
22
|
+
options = {
|
23
|
+
queue: job.queue_name,
|
24
|
+
cron: job.cron,
|
25
|
+
schedule_id: job.schedule_id
|
26
|
+
}
|
27
|
+
|
28
|
+
options[:run_at] = Time.at(timestamp) if timestamp
|
29
|
+
options[:priority] = job.priority if job.respond_to?(:priority)
|
30
|
+
wrapper = ::ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(job.serialize)
|
31
|
+
delayed_job = Delayed::Job.enqueue(wrapper, options)
|
32
|
+
job.provider_job_id = delayed_job.id if job.respond_to?(:provider_job_id=)
|
33
|
+
|
34
|
+
delayed_job
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -14,17 +14,20 @@ module Marty
|
|
14
14
|
lifecycle.before(:error) do |worker, job, &block|
|
15
15
|
if cron?(job)
|
16
16
|
begin
|
17
|
-
|
18
|
-
|
17
|
+
schedule = ::Marty::BackgroundJob::Schedule.find_by(id: job.schedule_id)
|
18
|
+
|
19
|
+
if schedule&.on?
|
20
|
+
job.cron = schedule.cron
|
21
|
+
job.schedule_id = schedule.id
|
22
|
+
else
|
23
|
+
job.cron = nil
|
24
|
+
job.schedule_id = nil
|
19
25
|
end
|
20
|
-
job_class_name = job_class_str.gsub('job_class:', '').strip
|
21
|
-
job_class = job_class_name.constantize
|
22
|
-
job.cron = job_class.cron_expression
|
23
26
|
rescue StandardError
|
24
27
|
end
|
25
28
|
else
|
26
29
|
# No cron job - proceed as normal
|
27
|
-
block
|
30
|
+
block&.call(worker, job)
|
28
31
|
end
|
29
32
|
end
|
30
33
|
end
|
data/lib/marty/monkey.rb
CHANGED
@@ -396,5 +396,16 @@ end
|
|
396
396
|
|
397
397
|
require 'delayed_cron_job'
|
398
398
|
require_relative './delayed_job/scheduled_job_plugin.rb'
|
399
|
+
require_relative './delayed_job/queue_adapter.rb'
|
399
400
|
|
400
401
|
Delayed::Worker.plugins << Marty::DelayedJob::ScheduledJobPlugin
|
402
|
+
|
403
|
+
if ActiveJob::QueueAdapters::DelayedJobAdapter.respond_to?(:enqueue)
|
404
|
+
ActiveJob::QueueAdapters::DelayedJobAdapter.extend(
|
405
|
+
Marty::DelayedJob::QueueAdapter
|
406
|
+
)
|
407
|
+
else
|
408
|
+
ActiveJob::QueueAdapters::DelayedJobAdapter.include(
|
409
|
+
Marty::DelayedJob::QueueAdapter
|
410
|
+
)
|
411
|
+
end
|
data/lib/marty/promise_job.rb
CHANGED
@@ -43,8 +43,9 @@ class Marty::PromiseJob < Struct.new(:promise,
|
|
43
43
|
|
44
44
|
# log "DONE #{Process.pid} #{promise.id} #{Time.now.to_f} #{res}"
|
45
45
|
rescue ::Delayed::WorkerTimeout => e
|
46
|
+
msg = ::Marty::Promise.timeout_message(promise)
|
46
47
|
timeout_error = StandardError.new(
|
47
|
-
|
48
|
+
"#{msg} (Triggered by #{e.class})"
|
48
49
|
)
|
49
50
|
timeout_error.set_backtrace(e.backtrace)
|
50
51
|
|
@@ -28,8 +28,9 @@ class Marty::PromiseRubyJob < Struct.new(:promise,
|
|
28
28
|
mod = module_name.constantize
|
29
29
|
res = { 'result' => mod.send(method_name, *method_args) }
|
30
30
|
rescue ::Delayed::WorkerTimeout => e
|
31
|
+
msg = ::Marty::Promise.timeout_message(promise)
|
31
32
|
timeout_error = StandardError.new(
|
32
|
-
|
33
|
+
"#{msg} (Triggered by #{e.class})"
|
33
34
|
)
|
34
35
|
timeout_error.set_backtrace(e.backtrace)
|
35
36
|
|
data/lib/marty/version.rb
CHANGED
data/spec/dummy/.foreman
ADDED
data/spec/dummy/Procfile
ADDED