solid_job_monitor 1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +106 -0
- data/Rakefile +8 -0
- data/app/assets/config/mission_control_jobs_manifest.js +4 -0
- data/app/assets/stylesheets/mission_control/jobs/application.css +16 -0
- data/app/assets/stylesheets/mission_control/jobs/bulma.min.css +21013 -0
- data/app/assets/stylesheets/mission_control/jobs/forms.css +8 -0
- data/app/assets/stylesheets/mission_control/jobs/jobs.css +68 -0
- data/app/controllers/concerns/mission_control/jobs/adapter_features.rb +24 -0
- data/app/controllers/concerns/mission_control/jobs/application_scoped.rb +38 -0
- data/app/controllers/concerns/mission_control/jobs/basic_authentication.rb +33 -0
- data/app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb +17 -0
- data/app/controllers/concerns/mission_control/jobs/job_filters.rb +41 -0
- data/app/controllers/concerns/mission_control/jobs/job_scoped.rb +16 -0
- data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +36 -0
- data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +12 -0
- data/app/controllers/mission_control/jobs/application_controller.rb +32 -0
- data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +20 -0
- data/app/controllers/mission_control/jobs/bulk_retries_controller.rb +10 -0
- data/app/controllers/mission_control/jobs/discards_controller.rb +18 -0
- data/app/controllers/mission_control/jobs/dispatches_controller.rb +18 -0
- data/app/controllers/mission_control/jobs/jobs_controller.rb +54 -0
- data/app/controllers/mission_control/jobs/progresses_controller.rb +27 -0
- data/app/controllers/mission_control/jobs/queues/pauses_controller.rb +15 -0
- data/app/controllers/mission_control/jobs/queues_controller.rb +16 -0
- data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +38 -0
- data/app/controllers/mission_control/jobs/retries_controller.rb +13 -0
- data/app/controllers/mission_control/jobs/stops_controller.rb +22 -0
- data/app/controllers/mission_control/jobs/workers_controller.rb +23 -0
- data/app/helpers/mission_control/jobs/application_helper.rb +9 -0
- data/app/helpers/mission_control/jobs/dates_helper.rb +19 -0
- data/app/helpers/mission_control/jobs/interface_helper.rb +23 -0
- data/app/helpers/mission_control/jobs/jobs_helper.rb +94 -0
- data/app/helpers/mission_control/jobs/navigation_helper.rb +60 -0
- data/app/javascript/mission_control/jobs/application.js +4 -0
- data/app/javascript/mission_control/jobs/controllers/application.js +9 -0
- data/app/javascript/mission_control/jobs/controllers/form_controller.js +21 -0
- data/app/javascript/mission_control/jobs/controllers/index.js +11 -0
- data/app/javascript/mission_control/jobs/controllers/progress_controller.js +55 -0
- data/app/javascript/mission_control/jobs/helpers/debounce_helpers.js +9 -0
- data/app/javascript/mission_control/jobs/helpers/index.js +1 -0
- data/app/models/mission_control/jobs/application_record.rb +7 -0
- data/app/models/mission_control/jobs/current.rb +3 -0
- data/app/models/mission_control/jobs/job_progress.rb +7 -0
- data/app/models/mission_control/jobs/page.rb +48 -0
- data/app/models/mission_control/jobs/recurring_task.rb +25 -0
- data/app/models/mission_control/jobs/worker.rb +17 -0
- data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
- data/app/views/layouts/mission_control/jobs/_flash.html.erb +9 -0
- data/app/views/layouts/mission_control/jobs/_navigation.html.erb +9 -0
- data/app/views/layouts/mission_control/jobs/application.html.erb +27 -0
- data/app/views/layouts/mission_control/jobs/application_selection/_applications.html.erb +13 -0
- data/app/views/layouts/mission_control/jobs/application_selection/_servers.html.erb +15 -0
- data/app/views/mission_control/jobs/jobs/_error_information.html.erb +22 -0
- data/app/views/mission_control/jobs/jobs/_filters.html.erb +55 -0
- data/app/views/mission_control/jobs/jobs/_general_information.html.erb +85 -0
- data/app/views/mission_control/jobs/jobs/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/_progress_bar.html.erb +16 -0
- data/app/views/mission_control/jobs/jobs/_raw_data.html.erb +4 -0
- data/app/views/mission_control/jobs/jobs/_title.html.erb +22 -0
- data/app/views/mission_control/jobs/jobs/_toolbar.html.erb +18 -0
- data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +3 -0
- data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +8 -0
- data/app/views/mission_control/jobs/jobs/failed/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb +14 -0
- data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +7 -0
- data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +2 -0
- data/app/views/mission_control/jobs/jobs/in_progress/_actions.html.erb +6 -0
- data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/index.html.erb +19 -0
- data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/show.html.erb +6 -0
- data/app/views/mission_control/jobs/queues/_actions.html.erb +7 -0
- data/app/views/mission_control/jobs/queues/_job.html.erb +15 -0
- data/app/views/mission_control/jobs/queues/_queue.html.erb +16 -0
- data/app/views/mission_control/jobs/queues/_queue_title.html.erb +17 -0
- data/app/views/mission_control/jobs/queues/index.html.erb +16 -0
- data/app/views/mission_control/jobs/queues/show.html.erb +25 -0
- data/app/views/mission_control/jobs/recurring_tasks/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb +37 -0
- data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +22 -0
- data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +10 -0
- data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +18 -0
- data/app/views/mission_control/jobs/recurring_tasks/show.html.erb +14 -0
- data/app/views/mission_control/jobs/shared/_job.html.erb +27 -0
- data/app/views/mission_control/jobs/shared/_jobs.html.erb +14 -0
- data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +7 -0
- data/app/views/mission_control/jobs/workers/_configuration.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/_raw_data.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/_title.html.erb +11 -0
- data/app/views/mission_control/jobs/workers/_worker.html.erb +21 -0
- data/app/views/mission_control/jobs/workers/_workers_page.html.erb +15 -0
- data/app/views/mission_control/jobs/workers/index.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/show.html.erb +14 -0
- data/config/importmap.rb +6 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20260828105144_create_mission_control_job_progresses.rb +16 -0
- data/lib/active_job/errors/invalid_operation.rb +5 -0
- data/lib/active_job/errors/job_not_found_error.rb +14 -0
- data/lib/active_job/errors/query_error.rb +5 -0
- data/lib/active_job/executing.rb +40 -0
- data/lib/active_job/execution_error.rb +8 -0
- data/lib/active_job/failed.rb +7 -0
- data/lib/active_job/job_proxy.rb +38 -0
- data/lib/active_job/jobs_relation.rb +318 -0
- data/lib/active_job/progress_reporting.rb +21 -0
- data/lib/active_job/querying.rb +44 -0
- data/lib/active_job/queue.rb +62 -0
- data/lib/active_job/queue_adapters/async_ext.rb +49 -0
- data/lib/active_job/queue_adapters/resque_ext.rb +317 -0
- data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +52 -0
- data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +41 -0
- data/lib/active_job/queue_adapters/solid_queue_ext.rb +313 -0
- data/lib/active_job/queues.rb +33 -0
- data/lib/mission_control/jobs/adapter.rb +156 -0
- data/lib/mission_control/jobs/application.rb +20 -0
- data/lib/mission_control/jobs/applications.rb +8 -0
- data/lib/mission_control/jobs/arguments_filter.rb +24 -0
- data/lib/mission_control/jobs/authentication.rb +67 -0
- data/lib/mission_control/jobs/console/connect_to.rb +15 -0
- data/lib/mission_control/jobs/console/context.rb +11 -0
- data/lib/mission_control/jobs/console/jobs_help.rb +23 -0
- data/lib/mission_control/jobs/engine.rb +118 -0
- data/lib/mission_control/jobs/errors/incompatible_adapter.rb +2 -0
- data/lib/mission_control/jobs/errors/resource_not_found.rb +2 -0
- data/lib/mission_control/jobs/i18n_config.rb +17 -0
- data/lib/mission_control/jobs/identified_by_name.rb +18 -0
- data/lib/mission_control/jobs/identified_elements.rb +23 -0
- data/lib/mission_control/jobs/job_stopped_error.rb +5 -0
- data/lib/mission_control/jobs/progress/null_store.rb +25 -0
- data/lib/mission_control/jobs/progress/reporter.rb +63 -0
- data/lib/mission_control/jobs/progress/store.rb +72 -0
- data/lib/mission_control/jobs/progress.rb +17 -0
- data/lib/mission_control/jobs/server/recurring_tasks.rb +15 -0
- data/lib/mission_control/jobs/server/serializable.rb +24 -0
- data/lib/mission_control/jobs/server/workers.rb +13 -0
- data/lib/mission_control/jobs/server.rb +27 -0
- data/lib/mission_control/jobs/tasks.rb +8 -0
- data/lib/mission_control/jobs/version.rb +5 -0
- data/lib/mission_control/jobs/workers_relation.rb +78 -0
- data/lib/mission_control/jobs.rb +43 -0
- data/lib/resque/thread_safe_redis.rb +34 -0
- data/lib/solid_job_monitor.rb +6 -0
- metadata +527 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require "importmap-rails"
|
|
2
|
+
require "turbo-rails"
|
|
3
|
+
require "stimulus-rails"
|
|
4
|
+
|
|
5
|
+
module MissionControl
|
|
6
|
+
module Jobs
|
|
7
|
+
class Engine < ::Rails::Engine
|
|
8
|
+
isolate_namespace MissionControl::Jobs
|
|
9
|
+
|
|
10
|
+
rake_tasks do
|
|
11
|
+
load "mission_control/jobs/tasks.rb"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
initializer "mission_control-jobs.middleware" do |app|
|
|
15
|
+
if app.config.api_only
|
|
16
|
+
config.middleware.use ActionDispatch::Flash
|
|
17
|
+
config.middleware.use ::Rack::MethodOverride
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
config.mission_control = ActiveSupport::OrderedOptions.new unless config.try(:mission_control)
|
|
22
|
+
config.mission_control.jobs = ActiveSupport::OrderedOptions.new
|
|
23
|
+
|
|
24
|
+
config.before_initialize do
|
|
25
|
+
config.mission_control.jobs.applications = MissionControl::Jobs::Applications.new
|
|
26
|
+
config.mission_control.jobs.backtrace_cleaner ||= Rails::BacktraceCleaner.new
|
|
27
|
+
|
|
28
|
+
config.mission_control.jobs.each do |key, value|
|
|
29
|
+
MissionControl::Jobs.public_send("#{key}=", value)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if MissionControl::Jobs.adapters.empty?
|
|
33
|
+
MissionControl::Jobs.adapters << (config.active_job.queue_adapter || :async)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
initializer "mission_control-jobs.http_basic_auth" do |app|
|
|
38
|
+
MissionControl::Jobs.http_basic_auth_user ||= app.credentials.dig(:mission_control, :http_basic_auth_user)
|
|
39
|
+
MissionControl::Jobs.http_basic_auth_password ||= app.credentials.dig(:mission_control, :http_basic_auth_password)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
initializer "mission_control-jobs.active_job.extensions", before: "active_job.set_configs" do
|
|
43
|
+
ActiveSupport.on_load :active_job do
|
|
44
|
+
include ActiveJob::Querying
|
|
45
|
+
include ActiveJob::Executing
|
|
46
|
+
include ActiveJob::Failed
|
|
47
|
+
include ActiveJob::ProgressReporting
|
|
48
|
+
ActiveJob.extend ActiveJob::Querying::Root
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
initializer "mission_control-jobs.append_migrations" do |app|
|
|
53
|
+
next if app.root.to_s.start_with?(root.to_s)
|
|
54
|
+
|
|
55
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
56
|
+
app.config.paths["db/migrate"] << expanded_path
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
config.before_initialize do
|
|
61
|
+
if MissionControl::Jobs.adapters.include?(:resque)
|
|
62
|
+
require "resque/thread_safe_redis"
|
|
63
|
+
ActiveJob::QueueAdapters::ResqueAdapter.prepend ActiveJob::QueueAdapters::ResqueExt
|
|
64
|
+
Resque.prepend Resque::ThreadSafeRedis
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if MissionControl::Jobs.adapters.include?(:solid_queue)
|
|
68
|
+
ActiveJob::QueueAdapters::SolidQueueAdapter.prepend ActiveJob::QueueAdapters::SolidQueueExt
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
ActiveJob::QueueAdapters::AsyncAdapter.include ActiveJob::QueueAdapters::AsyncExt
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
config.after_initialize do |app|
|
|
75
|
+
if MissionControl::Jobs.applications.empty?
|
|
76
|
+
queue_adapters_by_name = MissionControl::Jobs.adapters.each_with_object({}) do |adapter, hsh|
|
|
77
|
+
hsh[adapter] = ActiveJob::QueueAdapters.lookup(adapter).new
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
MissionControl::Jobs.applications.add(app.class.module_parent.name, queue_adapters_by_name)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
console do
|
|
85
|
+
require "irb"
|
|
86
|
+
|
|
87
|
+
IRB::Command.register :connect_to, Console::ConnectTo
|
|
88
|
+
IRB::Command.register :jobs_help, Console::JobsHelp
|
|
89
|
+
|
|
90
|
+
IRB::Context.prepend(MissionControl::Jobs::Console::Context)
|
|
91
|
+
|
|
92
|
+
MissionControl::Jobs.delay_between_bulk_operation_batches = 2
|
|
93
|
+
MissionControl::Jobs.logger = ActiveSupport::Logger.new(STDOUT)
|
|
94
|
+
|
|
95
|
+
if MissionControl::Jobs.show_console_help
|
|
96
|
+
puts "\n\nType 'jobs_help' to see how to connect to the available job servers to manage jobs\n\n"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
initializer "mission_control-jobs.assets" do |app|
|
|
101
|
+
app.config.assets.paths << root.join("app/assets/stylesheets")
|
|
102
|
+
app.config.assets.paths << root.join("app/javascript")
|
|
103
|
+
app.config.assets.precompile += %w[ mission_control_jobs_manifest ]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
initializer "mission_control-jobs.importmap", after: "importmap" do |app|
|
|
107
|
+
MissionControl::Jobs.importmap.draw(root.join("config/importmap.rb"))
|
|
108
|
+
if app.config.importmap.sweep_cache && app.config.reloading_enabled?
|
|
109
|
+
MissionControl::Jobs.importmap.cache_sweeper(watches: root.join("app/javascript"))
|
|
110
|
+
|
|
111
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
112
|
+
before_action { MissionControl::Jobs.importmap.cache_sweeper.execute_if_updated }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class MissionControl::Jobs::I18nConfig < ::I18n::Config
|
|
2
|
+
AVAILABLE_LOCALES = [ :en ]
|
|
3
|
+
AVAILABLE_LOCALES_SET = [ :en, "en" ]
|
|
4
|
+
DEFAULT_LOCALE = :en
|
|
5
|
+
|
|
6
|
+
def available_locales
|
|
7
|
+
AVAILABLE_LOCALES
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def available_locales_set
|
|
11
|
+
AVAILABLE_LOCALES_SET
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def default_locale
|
|
15
|
+
DEFAULT_LOCALE
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module MissionControl::Jobs::IdentifiedByName
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
attr_reader :name
|
|
6
|
+
alias to_s name
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(name:)
|
|
10
|
+
@name = name.to_s
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def id
|
|
14
|
+
name.parameterize
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
alias to_param id
|
|
18
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# A collection of elements offering a Hash-like access based on
|
|
2
|
+
# their +id+.
|
|
3
|
+
class MissionControl::Jobs::IdentifiedElements
|
|
4
|
+
include Enumerable
|
|
5
|
+
|
|
6
|
+
delegate :[], :empty?, to: :elements
|
|
7
|
+
delegate :each, :last, :length, to: :to_a
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@elements = HashWithIndifferentAccess.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def <<(item)
|
|
14
|
+
@elements[item.id] = item
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_a
|
|
18
|
+
@elements.values
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
attr_reader :elements
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class MissionControl::Jobs::Progress::NullStore
|
|
2
|
+
def upsert(**)
|
|
3
|
+
nil
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def find(_job_id)
|
|
7
|
+
nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find_multi(_job_ids)
|
|
11
|
+
{}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def request_stop(_job_id)
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def stop_requested?(_job_id)
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def complete(_job_id)
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
class MissionControl::Jobs::Progress::Reporter
|
|
2
|
+
def initialize(job_id)
|
|
3
|
+
@job_id = job_id
|
|
4
|
+
@current = 0
|
|
5
|
+
@total = nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def register(total:, message: nil)
|
|
9
|
+
check_stopped!
|
|
10
|
+
@total = total
|
|
11
|
+
@current = 0
|
|
12
|
+
persist(percent: computed_percent(0), message: message)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def update(current: nil, total: nil, percent: nil, message: nil)
|
|
16
|
+
check_stopped!
|
|
17
|
+
@current = current unless current.nil?
|
|
18
|
+
@total = total unless total.nil?
|
|
19
|
+
persist(percent: computed_percent(percent), message: message)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def increment(by: 1)
|
|
23
|
+
check_stopped!
|
|
24
|
+
@current = @current.to_i + by
|
|
25
|
+
persist(percent: computed_percent(nil))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def check_stopped!
|
|
29
|
+
raise MissionControl::Jobs::JobStoppedError.new(@job_id) if store.stop_requested?(@job_id)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def complete
|
|
33
|
+
store.complete(@job_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
def persist(percent:, message: nil)
|
|
38
|
+
store.upsert(
|
|
39
|
+
job_id: @job_id,
|
|
40
|
+
current: @current.to_i,
|
|
41
|
+
total: @total,
|
|
42
|
+
percent: percent,
|
|
43
|
+
message: message
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def computed_percent(explicit)
|
|
48
|
+
return clamp_percent(explicit) unless explicit.nil?
|
|
49
|
+
return nil if @total.nil? || @total.to_i <= 0
|
|
50
|
+
|
|
51
|
+
clamp_percent(((@current.to_f / @total) * 100).round)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def clamp_percent(value)
|
|
55
|
+
return nil if value.nil?
|
|
56
|
+
|
|
57
|
+
value.to_i.clamp(0, 100)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def store
|
|
61
|
+
MissionControl::Jobs::Progress::Store.current
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class MissionControl::Jobs::Progress::Store
|
|
2
|
+
class << self
|
|
3
|
+
def current
|
|
4
|
+
@current ||= available? ? new : null_store
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def available?
|
|
8
|
+
return @available unless @available.nil?
|
|
9
|
+
|
|
10
|
+
@available = MissionControl::Jobs::JobProgress.table_exists?
|
|
11
|
+
rescue ActiveRecord::ActiveRecordError
|
|
12
|
+
@available = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def reset!
|
|
16
|
+
@current = nil
|
|
17
|
+
@available = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
def null_store
|
|
22
|
+
warn_if_missing
|
|
23
|
+
MissionControl::Jobs::Progress::NullStore.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def warn_if_missing
|
|
27
|
+
return if @warned
|
|
28
|
+
|
|
29
|
+
@warned = true
|
|
30
|
+
logger = (defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) || MissionControl::Jobs.logger
|
|
31
|
+
logger&.warn(
|
|
32
|
+
"[solid_job_monitor] mission_control_job_progresses table is missing. " \
|
|
33
|
+
"Run rails db:migrate to enable job progress and cooperative stop."
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def upsert(job_id:, current: 0, total: nil, percent: nil, message: nil)
|
|
39
|
+
record = MissionControl::Jobs::JobProgress.find_or_initialize_by(job_id: job_id)
|
|
40
|
+
record.current = current
|
|
41
|
+
record.total = total unless total.nil?
|
|
42
|
+
record.percent = percent unless percent.nil?
|
|
43
|
+
record.message = message unless message.nil?
|
|
44
|
+
record.save!
|
|
45
|
+
record
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def find(job_id)
|
|
49
|
+
MissionControl::Jobs::JobProgress.find_by(job_id: job_id)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_multi(job_ids)
|
|
53
|
+
return {} if job_ids.blank?
|
|
54
|
+
|
|
55
|
+
MissionControl::Jobs::JobProgress.where(job_id: job_ids).index_by(&:job_id)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def request_stop(job_id)
|
|
59
|
+
record = MissionControl::Jobs::JobProgress.find_or_initialize_by(job_id: job_id)
|
|
60
|
+
record.stop_requested = true
|
|
61
|
+
record.save!
|
|
62
|
+
record
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def stop_requested?(job_id)
|
|
66
|
+
MissionControl::Jobs::JobProgress.where(job_id: job_id, stop_requested: true).exists?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def complete(job_id)
|
|
70
|
+
MissionControl::Jobs::JobProgress.where(job_id: job_id).delete_all
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module MissionControl::Jobs::Progress
|
|
2
|
+
def self.available?
|
|
3
|
+
Store.available?
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def self.find(job_id)
|
|
7
|
+
Store.current.find(job_id)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.find_multi(job_ids)
|
|
11
|
+
Store.current.find_multi(job_ids)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.request_stop(job_id)
|
|
15
|
+
Store.current.request_stop(job_id)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module MissionControl::Jobs::Server::RecurringTasks
|
|
2
|
+
def recurring_tasks
|
|
3
|
+
queue_adapter.recurring_tasks.collect do |task|
|
|
4
|
+
MissionControl::Jobs::RecurringTask.new(queue_adapter: queue_adapter, **task)
|
|
5
|
+
end.sort_by(&:id)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def find_recurring_task(task_id)
|
|
9
|
+
if task = queue_adapter.find_recurring_task(task_id)
|
|
10
|
+
MissionControl::Jobs::RecurringTask.new(queue_adapter: queue_adapter, **task)
|
|
11
|
+
else
|
|
12
|
+
raise MissionControl::Jobs::Errors::ResourceNotFound, "Recurring task with id '#{task_id}' not found"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module MissionControl::Jobs::Server::Serializable
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
class_methods do
|
|
5
|
+
# Loads a server from a locator string with the format +<application>:<server>+. For example:
|
|
6
|
+
#
|
|
7
|
+
# bc4:resque_chicago
|
|
8
|
+
#
|
|
9
|
+
# When the +<server>+ fragment is omitted it will return the first server for the application.
|
|
10
|
+
def from_global_id(global_id)
|
|
11
|
+
app_id, server_id = global_id.split(":")
|
|
12
|
+
|
|
13
|
+
application = MissionControl::Jobs.applications[app_id] or raise MissionControl::Jobs::Errors::ResourceNotFound, "No application with id #{app_id} found"
|
|
14
|
+
server = server_id ? application.servers[server_id] : application.servers.first
|
|
15
|
+
|
|
16
|
+
server or raise MissionControl::Jobs::Errors::ResourceNotFound, "No server for #{global_id} found"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_global_id
|
|
21
|
+
suffix = ":#{id}" if application.servers.many?
|
|
22
|
+
"#{application&.id}#{suffix}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module MissionControl::Jobs::Server::Workers
|
|
2
|
+
def workers_relation
|
|
3
|
+
MissionControl::Jobs::WorkersRelation.new(queue_adapter: queue_adapter)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def find_worker(worker_id)
|
|
7
|
+
if worker = queue_adapter.find_worker(worker_id)
|
|
8
|
+
MissionControl::Jobs::Worker.new(queue_adapter: queue_adapter, **worker)
|
|
9
|
+
else
|
|
10
|
+
raise MissionControl::Jobs::Errors::ResourceNotFound, "Worker with id '#{worker_id}' not found"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "active_job/queue_adapter"
|
|
2
|
+
|
|
3
|
+
class MissionControl::Jobs::Server
|
|
4
|
+
include MissionControl::Jobs::IdentifiedByName
|
|
5
|
+
include Serializable, RecurringTasks, Workers
|
|
6
|
+
|
|
7
|
+
attr_reader :name, :queue_adapter, :application, :backtrace_cleaner
|
|
8
|
+
|
|
9
|
+
def initialize(name:, queue_adapter:, application:, backtrace_cleaner: nil)
|
|
10
|
+
super(name: name)
|
|
11
|
+
@queue_adapter = queue_adapter
|
|
12
|
+
@application = application
|
|
13
|
+
@backtrace_cleaner = backtrace_cleaner || MissionControl::Jobs.backtrace_cleaner
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def activating(&block)
|
|
17
|
+
previous_adapter = ActiveJob::Base.current_queue_adapter
|
|
18
|
+
ActiveJob::Base.current_queue_adapter = queue_adapter
|
|
19
|
+
queue_adapter.activating(&block)
|
|
20
|
+
ensure
|
|
21
|
+
ActiveJob::Base.current_queue_adapter = previous_adapter
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def queue_adapter_name
|
|
25
|
+
ActiveJob.adapter_name(queue_adapter).underscore.to_sym
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# A relation of workers.
|
|
2
|
+
#
|
|
3
|
+
# Relations are enumerable, so you can use +Enumerable+ methods on them.
|
|
4
|
+
# Notice however that using these methods will imply loading all the relation
|
|
5
|
+
# in memory, which could introduce performance concerns.
|
|
6
|
+
class MissionControl::Jobs::WorkersRelation
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
attr_accessor :offset_value, :limit_value
|
|
10
|
+
|
|
11
|
+
delegate :last, :[], :to_s, :reverse, to: :to_a
|
|
12
|
+
|
|
13
|
+
ALL_WORKERS_LIMIT = 100_000_000 # When no limit value it defaults to "all workers"
|
|
14
|
+
|
|
15
|
+
def initialize(queue_adapter:)
|
|
16
|
+
@queue_adapter = queue_adapter
|
|
17
|
+
|
|
18
|
+
set_defaults
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def offset(offset)
|
|
22
|
+
clone_with offset_value: offset
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def limit(limit)
|
|
26
|
+
clone_with limit_value: limit
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def each(&block)
|
|
30
|
+
workers.each(&block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def reload
|
|
34
|
+
@count = @workers = nil
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def count
|
|
39
|
+
if loaded?
|
|
40
|
+
to_a.length
|
|
41
|
+
else
|
|
42
|
+
query_count
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def empty?
|
|
47
|
+
count == 0
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
alias length count
|
|
51
|
+
alias size count
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
def set_defaults
|
|
55
|
+
self.offset_value = 0
|
|
56
|
+
self.limit_value = ALL_WORKERS_LIMIT
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def workers
|
|
60
|
+
@workers ||= @queue_adapter.fetch_workers(self)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def query_count
|
|
64
|
+
@count ||= @queue_adapter.count_workers(self)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def loaded?
|
|
68
|
+
!@workers.nil?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def clone_with(**properties)
|
|
72
|
+
dup.reload.tap do |relation|
|
|
73
|
+
properties.each do |key, value|
|
|
74
|
+
relation.send("#{key}=", value)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "mission_control/jobs/version"
|
|
2
|
+
require "mission_control/jobs/engine"
|
|
3
|
+
|
|
4
|
+
require "zeitwerk"
|
|
5
|
+
|
|
6
|
+
loader = Zeitwerk::Loader.new
|
|
7
|
+
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
|
8
|
+
loader.push_dir(File.expand_path("..", __dir__))
|
|
9
|
+
loader.ignore("#{File.expand_path("..", __dir__)}/resque")
|
|
10
|
+
loader.ignore("#{File.expand_path("..", __dir__)}/solid_job_monitor.rb")
|
|
11
|
+
loader.ignore("#{File.expand_path("..", __dir__)}/mission_control/jobs/tasks.rb")
|
|
12
|
+
loader.ignore("#{File.expand_path("..", __dir__)}/generators")
|
|
13
|
+
loader.setup
|
|
14
|
+
|
|
15
|
+
module MissionControl
|
|
16
|
+
module Jobs
|
|
17
|
+
mattr_accessor :adapters, default: Set.new
|
|
18
|
+
mattr_accessor :applications, default: MissionControl::Jobs::Applications.new
|
|
19
|
+
mattr_accessor :base_controller_class, default: "::ApplicationController"
|
|
20
|
+
|
|
21
|
+
mattr_accessor :internal_query_count_limit, default: 500_000 # Hard limit to keep unlimited count queries fast enough
|
|
22
|
+
mattr_accessor :delay_between_bulk_operation_batches, default: 0
|
|
23
|
+
mattr_accessor :scheduled_job_delay_threshold, default: 1.minute
|
|
24
|
+
|
|
25
|
+
mattr_accessor :logger, default: ActiveSupport::Logger.new(nil)
|
|
26
|
+
|
|
27
|
+
mattr_accessor :show_console_help, default: true
|
|
28
|
+
mattr_accessor :backtrace_cleaner
|
|
29
|
+
mattr_accessor :back_to_main_app_path
|
|
30
|
+
|
|
31
|
+
mattr_accessor :importmap, default: Importmap::Map.new
|
|
32
|
+
|
|
33
|
+
mattr_accessor :http_basic_auth_user
|
|
34
|
+
mattr_accessor :http_basic_auth_password
|
|
35
|
+
mattr_accessor :http_basic_auth_enabled, default: true
|
|
36
|
+
|
|
37
|
+
mattr_accessor :filter_arguments, default: []
|
|
38
|
+
|
|
39
|
+
def self.job_arguments_filter
|
|
40
|
+
MissionControl::Jobs::ArgumentsFilter.new(filter_arguments)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Set and access +Resque.redis+ in a thread-safe way.
|
|
2
|
+
module Resque::ThreadSafeRedis
|
|
3
|
+
thread_mattr_accessor :thread_resque_override
|
|
4
|
+
|
|
5
|
+
def self.resque_override
|
|
6
|
+
self.thread_resque_override ||= ResqueOverride.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def redis
|
|
10
|
+
Resque::ThreadSafeRedis.resque_override.data_store_override || super
|
|
11
|
+
end
|
|
12
|
+
alias :data_store :redis
|
|
13
|
+
|
|
14
|
+
def with_per_thread_redis_override(redis_instance, &block)
|
|
15
|
+
Resque::ThreadSafeRedis.resque_override.enable_with(redis_instance, &block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class ResqueOverride
|
|
19
|
+
include Resque
|
|
20
|
+
|
|
21
|
+
attr_accessor :data_store_override
|
|
22
|
+
|
|
23
|
+
def enable_with(server, &block)
|
|
24
|
+
previous_redis, previous_data_store_override = redis, data_store_override
|
|
25
|
+
self.redis = server
|
|
26
|
+
self.data_store_override = @data_store
|
|
27
|
+
|
|
28
|
+
block.call
|
|
29
|
+
ensure
|
|
30
|
+
self.redis = previous_redis
|
|
31
|
+
self.data_store_override = previous_data_store_override
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|