sidekiq_adhoc_job 0.1.3 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d343fd5493049cf0f38225bfde6a3d5329e1bf9aa97d9c57a78b2896cd615ad1
4
- data.tar.gz: 3e5b08a0e5a81ab52070158ec10e64c6f92a34726244d356a096259981a0d62d
3
+ metadata.gz: 8c0e73d782590b6b44e800550261cb9c64414eac41a516542beeee8f77345b0a
4
+ data.tar.gz: 063c9caff1f9b63a7fac67bb8e033153a60531c9bbde195bc1aeb2d509dcd9b9
5
5
  SHA512:
6
- metadata.gz: f0b0a21ab330712ab59a2c0a356f5ca6c1c31ff9a704b9c08ab3b5ced2f22d11deee0a831c4e1bac51592e9420f74cbfea18aa4bab5f184e44e51f9e36c17d2a
7
- data.tar.gz: c37d32257e371b2cf4851733ee3f9f32a31d495e372801411c6c491aae2ce482f654e409561e8300dfa602d63d01506b6962a4e6201fcd817d8bb18fbd360964
6
+ metadata.gz: 27604c34ae4f16f120ed722dcb92fe4d5e8ce271f2353260f4265212bbd89b7e0ab28a1d5ac606cbfd2b66afab1ed0abeefb61ff2790dc52e09f0d6ff9b5f19a
7
+ data.tar.gz: 9056e87feb58fb3d38e70eb8a0d7689123bc98ee6e22d0d83f5fcdfb14497533859dd45c7bf8e58dfb8e25514205d27081e74bb3ff5e8c178e3185a82f596f89
@@ -2,6 +2,8 @@ require 'sidekiq'
2
2
  require 'sidekiq/web'
3
3
 
4
4
  require 'sidekiq_adhoc_job/utils/string'
5
+ require 'sidekiq_adhoc_job/utils/class_inspector'
6
+ require 'sidekiq_adhoc_job/strategy'
5
7
  require 'sidekiq_adhoc_job/worker_classes_loader'
6
8
  require 'sidekiq_adhoc_job/web/job_presenter'
7
9
  require 'sidekiq_adhoc_job/services/schedule_adhoc_job'
@@ -9,7 +11,12 @@ require 'sidekiq_adhoc_job/web'
9
11
 
10
12
  module SidekiqAdhocJob
11
13
 
12
- InvalidConfigurationError ||= Class.new(RuntimeError)
14
+ StringUtil ||= Utils::String
15
+
16
+ module Strategies
17
+ autoload :Default, 'sidekiq_adhoc_job/strategies/default'
18
+ autoload :ActiveJob, 'sidekiq_adhoc_job/strategies/active_job'
19
+ end
13
20
 
14
21
  def self.configure
15
22
  @_config = Configuration.new
@@ -21,24 +28,39 @@ module SidekiqAdhocJob
21
28
  end
22
29
 
23
30
  def self.init
24
- raise InvalidConfigurationError, 'Must configure before init' unless @_config&.configured?
25
-
26
- SidekiqAdhocJob::WorkerClassesLoader.load(@_config.module_names)
31
+ SidekiqAdhocJob::WorkerClassesLoader.load(@_config.module_names, load_paths: @_config.load_paths, strategy: @_config.strategy)
27
32
 
28
33
  Sidekiq::Web.register(SidekiqAdhocJob::Web)
29
34
  Sidekiq::Web.tabs['adhoc_jobs'] = 'adhoc-jobs'
30
35
  Sidekiq::Web.locales << File.expand_path('sidekiq_adhoc_job/web/locales', __dir__)
31
36
  end
32
37
 
38
+ def self.strategies
39
+ @_strategies ||= []
40
+ end
41
+
33
42
  class Configuration
34
- attr_accessor :module_names
43
+ attr_accessor :load_paths, :module_names, :strategy_name
35
44
 
36
45
  def initialize
46
+ @load_paths = []
37
47
  @module_names = []
48
+ @strategy_name = :default
49
+ end
50
+
51
+ def module_names
52
+ Array(@module_names).map(&:to_s)
38
53
  end
39
54
 
40
- def configured?
41
- !@module_names.empty?
55
+ def strategy
56
+ @strategy ||= case strategy_name
57
+ when :default
58
+ SidekiqAdhocJob::Strategies::Default.new(module_names)
59
+ else
60
+ strategy_klass = SidekiqAdhocJob::Strategies.const_get(StringUtil.camelize(strategy_name.to_s).to_s)
61
+ raise InvalidConfigurationError, "Invalid strategy name" unless strategy_klass
62
+ strategy_klass.new(module_names)
63
+ end
42
64
  end
43
65
  end
44
66
 
@@ -9,22 +9,27 @@ module SidekiqAdhocJob
9
9
  acc
10
10
  end
11
11
  @worker_klass = WorkerClassesLoader.find_worker_klass(job_name)
12
+ @worker_klass_inspector = Utils::ClassInspector.new(worker_klass)
13
+ @allowed_params = worker_klass_inspector.required_parameters(:perform) + worker_klass_inspector.optional_parameters(:perform)
14
+
12
15
  parse_params
13
16
  end
14
17
 
15
18
  def call
16
- worker_klass.perform_async(*worker_params)
19
+ SidekiqAdhocJob.config.strategy.perform_async(worker_klass, *worker_params)
17
20
  end
18
21
 
19
22
  private
20
23
 
21
- attr_reader :request_params, :worker_klass, :allowed_params, :worker_params
24
+ attr_reader :request_params, :worker_klass, :worker_klass_inspector,
25
+ :allowed_params, :worker_params
22
26
 
23
27
  def parse_params
24
- @allowed_params = worker_klass.new.method(:perform).parameters.reject { |type, _| type == :rest }.flat_map(&:last)
25
- @worker_params = allowed_params.map { |key| StringUtil.parse(request_params[key]) }
28
+ @worker_params = allowed_params
29
+ .reject { |key| request_params[key].empty? }
30
+ .map { |key| StringUtil.parse(request_params[key], symbolize: true) }
26
31
  if !!request_params[:rest_args] && !request_params[:rest_args].empty?
27
- @worker_params += request_params[:rest_args].split(',').map { |arg| StringUtil.parse(arg.strip) }
32
+ @worker_params << StringUtil.parse_json(request_params[:rest_args].strip, symbolize: true)
28
33
  end
29
34
  end
30
35
 
@@ -0,0 +1,19 @@
1
+ module SidekiqAdhocJob
2
+ module Strategies
3
+ class ActiveJob
4
+ include SidekiqAdhocJob::Strategy
5
+
6
+ def worker_class?(klass)
7
+ klass.superclass&.name == 'ActiveJob::Base'
8
+ end
9
+
10
+ def get_queue_name(klass_name)
11
+ klass_name.queue_name
12
+ end
13
+
14
+ def perform_async(klass, *params)
15
+ klass.perform_later(*params)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module SidekiqAdhocJob
2
+ module Strategies
3
+ class Default
4
+ include SidekiqAdhocJob::Strategy
5
+
6
+ def worker_class?(klass)
7
+ klass.included_modules.include?(Sidekiq::Worker)
8
+ end
9
+
10
+ def get_queue_name(klass_name)
11
+ klass_name.sidekiq_options['queue']
12
+ end
13
+
14
+ def perform_async(klass, *params)
15
+ klass.perform_async(*params)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ module SidekiqAdhocJob
2
+ module Strategy
3
+ def self.included(base)
4
+ SidekiqAdhocJob.strategies << base
5
+
6
+ base.extend ClassMethods
7
+ base.class_eval do
8
+ end
9
+ end
10
+
11
+ attr_reader :module_names, :worker_klasses
12
+
13
+ StringUtil ||= SidekiqAdhocJob::Utils::String
14
+
15
+ def initialize(module_names)
16
+ @module_names = module_names
17
+ @worker_klasses = {}
18
+ end
19
+
20
+ def load
21
+ ObjectSpace.each_object(Class).each do |klass|
22
+ next unless klass
23
+
24
+ if worker_class?(klass) && allowed_namespace?(klass.name, allowlist: module_names)
25
+ @worker_klasses[worker_path_name(klass.name)] = klass
26
+ end
27
+ end
28
+ end
29
+
30
+ def allowed_namespace?(class_name, allowlist:)
31
+ return true if allowlist.empty? || allowlist.include?('Module') # allow any namespace
32
+
33
+ allowlist.any? { |prefix| class_name.start_with?(prefix) }
34
+ end
35
+
36
+ def worker_path_name(worker_name)
37
+ Utils::String.underscore(worker_name).gsub('/', '_')
38
+ end
39
+
40
+ module ClassMethods
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,50 @@
1
+ module SidekiqAdhocJob
2
+ module Utils
3
+ class ClassInspector
4
+
5
+ attr_reader :klass_name, :klass_obj, :method_parameters
6
+
7
+ def initialize(klass_name)
8
+ @klass_name = klass_name
9
+ @klass_obj = klass_name.new
10
+ @method_parameters = {}
11
+ end
12
+
13
+ def parameters(method_name)
14
+ return method_parameters[method_name] if method_parameters[method_name]
15
+
16
+ klass_method = klass_method(klass_obj.method(method_name))
17
+ params = klass_method
18
+ .parameters
19
+ .group_by { |type, _| type }
20
+ .inject({}) do |acc, (type, params)|
21
+ acc[type] = params.map(&:last)
22
+ acc
23
+ end
24
+
25
+ method_parameters[method_name] = params
26
+
27
+ params
28
+ end
29
+
30
+ def required_parameters(method_name)
31
+ parameters(method_name)[:req] || []
32
+ end
33
+
34
+ def optional_parameters(method_name)
35
+ parameters(method_name)[:opt] || []
36
+ end
37
+
38
+ def has_rest_parameter?(method_name)
39
+ !!parameters(method_name)[:rest]
40
+ end
41
+
42
+ def klass_method(method)
43
+ return method unless method.super_method
44
+
45
+ klass_method(method.super_method)
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -1,4 +1,5 @@
1
1
  # References: https://github.com/hanami/utils/blob/master/lib/hanami/utils/string.rb
2
+ # https://github.com/omniauth/omniauth/blob/cc0f5522621b4a372f4dff0aa608822aa082cb60/lib/omniauth.rb#L156
2
3
  module SidekiqAdhocJob
3
4
  module Utils
4
5
  class String
@@ -75,17 +76,29 @@ module SidekiqAdhocJob
75
76
  constant
76
77
  end
77
78
 
78
- def self.parse(input)
79
+ def self.camelize(word, first_letter_in_uppercase = true)
80
+ if first_letter_in_uppercase
81
+ word.to_s.gsub(%r{/(.?)}) { '::' + Regexp.last_match[1].upcase }.gsub(/(^|_)(.)/) { Regexp.last_match[2].upcase }
82
+ else
83
+ word.first + self.camelize(word)[1..-1]
84
+ end
85
+ end
86
+
87
+ def self.parse(input, symbolize: false)
79
88
  return unless input
80
89
 
81
90
  if input == 'true'
82
91
  true
83
92
  elsif input == 'false'
84
93
  false
94
+ elsif input == 'nil'
95
+ nil
85
96
  elsif (i = parse_integer(input))
86
97
  i
87
98
  elsif (f = parse_float(input))
88
99
  f
100
+ elsif (j = parse_json(input, symbolize: symbolize))
101
+ j
89
102
  else
90
103
  input
91
104
  end
@@ -103,6 +116,12 @@ module SidekiqAdhocJob
103
116
  nil
104
117
  end
105
118
 
119
+ def self.parse_json(input, symbolize: false)
120
+ JSON.parse(input, symbolize_names: symbolize)
121
+ rescue JSON::ParserError => _e
122
+ nil
123
+ end
124
+
106
125
  end
107
126
  end
108
127
  end
@@ -1,3 +1,3 @@
1
1
  module SidekiqAdhocJob
2
- VERSION = '0.1.3'.freeze
2
+ VERSION = '0.2.2'.freeze
3
3
  end
@@ -4,7 +4,7 @@ module SidekiqAdhocJob
4
4
  class JobPresenter
5
5
  include Sidekiq::WebHelpers
6
6
 
7
- attr_reader :name, :path_name, :queue, :has_rest_args, :args
7
+ attr_reader :name, :path_name, :queue, :required_args, :optional_args, :has_rest_args
8
8
 
9
9
  StringUtil ||= ::SidekiqAdhocJob::Utils::String
10
10
 
@@ -16,7 +16,6 @@ module SidekiqAdhocJob
16
16
  @required_args = args[:req] || []
17
17
  @optional_args = args[:opt] || []
18
18
  @has_rest_args = !!args[:rest]
19
- @args = @required_args + @optional_args
20
19
  end
21
20
 
22
21
  # Builds the presenter instances for the schedule hash
@@ -38,16 +37,9 @@ module SidekiqAdhocJob
38
37
  end
39
38
 
40
39
  def self.convert_klass_name_to_presenter(path_name, klass_name)
41
- klass_obj = klass_name.new
42
- queue = klass_name.sidekiq_options['queue']
43
- args = klass_obj
44
- .method(:perform)
45
- .parameters
46
- .group_by { |type, _| type }
47
- .inject({}) do |acc, (type, params)|
48
- acc[type] = params.map(&:last)
49
- acc
50
- end
40
+ queue = SidekiqAdhocJob.config.strategy.get_queue_name(klass_name)
41
+ class_inspector = SidekiqAdhocJob::Utils::ClassInspector.new(klass_name)
42
+ args = class_inspector.parameters(:perform)
51
43
  new(klass_name, path_name, queue, args)
52
44
  end
53
45
 
@@ -1,7 +1,8 @@
1
1
  en:
2
2
  adhoc_jobs: Adhoc Jobs
3
3
  adhoc_jobs_actions: Actions
4
- adhoc_jobs_arguments: Required Arguments
4
+ adhoc_jobs_required_arguments: Required Arguments
5
+ adhoc_jobs_optional_arguments: Optional Arguments
5
6
  adhoc_jobs_go_back: Go Back
6
7
  adhoc_jobs_has_rest_arguments: Has Rest Arguments
7
8
  adhoc_jobs_name: Job Name
@@ -6,7 +6,8 @@
6
6
  <tr>
7
7
  <th><%= t('adhoc_jobs_name') %></th>
8
8
  <th><%= t('adhoc_jobs_queue') %></th>
9
- <th><%= t('adhoc_jobs_arguments') %></th>
9
+ <th><%= t('adhoc_jobs_required_arguments') %></th>
10
+ <th><%= t('adhoc_jobs_optional_arguments') %></th>
10
11
  <th><%= t('adhoc_jobs_has_rest_arguments') %></th>
11
12
  <th><%= t('adhoc_jobs_actions') %></th>
12
13
  </tr>
@@ -17,7 +18,8 @@
17
18
  <tr>
18
19
  <td><%= job.name %></td>
19
20
  <td><%= job.queue %></td>
20
- <td><%= job.args.join(', ') %></td>
21
+ <td><%= job.required_args.join(', ') %></td>
22
+ <td><%= job.optional_args.join(', ') %></td>
21
23
  <td><%= job.has_rest_args %></td>
22
24
  <td class="text-center">
23
25
  <a class="btn btn-warn btn-xs" href="<%= root_path %>adhoc-jobs/<%= URI.escape(job.path_name) %>">
@@ -1,11 +1,13 @@
1
1
  <h3><%= t('adhoc_jobs') %></h3>
2
2
 
3
+ <h4><%= SidekiqAdhocJob::Utils::String.classify(@presented_job.path_name) %></h4>
4
+
3
5
  <form method="POST" action="<%= root_path %>adhoc-jobs/<%= URI.escape(@presented_job.path_name) %>/schedule">
4
6
  <input type="hidden" name="authenticity_token" value="<%= @csrf_token %>" />
5
- <% if @presented_job.args.empty? && !@presented_job.has_rest_args %>
7
+ <% if @presented_job.required_args.empty? && @presented_job.optional_args.empty? && !@presented_job.has_rest_args %>
6
8
  <p>No job arguments</p>
7
9
  <% else %>
8
- <% @presented_job.args.each do |arg| %>
10
+ <% @presented_job.required_args.each do |arg| %>
9
11
  <div class="form-group row">
10
12
  <label class="col-sm-2 col-form-label" for="<%= arg %>">*<%= arg %>:</label>
11
13
  <div class="col-sm-4">
@@ -13,9 +15,17 @@
13
15
  </div>
14
16
  </div>
15
17
  <% end %>
18
+ <% @presented_job.optional_args.each do |arg| %>
19
+ <div class="form-group row">
20
+ <label class="col-sm-2 col-form-label" for="<%= arg %>"><%= arg %>:</label>
21
+ <div class="col-sm-4">
22
+ <input class="form-control" type="text" name="<%= arg %>" id="<%= arg %>"/>
23
+ </div>
24
+ </div>
25
+ <% end %>
16
26
  <% if @presented_job.has_rest_args %>
17
27
  <div class="form-group row">
18
- <label class="col-sm-2 col-form-label" for="rest_args">*Rest arguments (please separate each argument by comma):</label>
28
+ <label class="col-sm-2 col-form-label" for="rest_args">*Rest arguments (please provide a json string representing the arguments):</label>
19
29
  <div class="col-sm-4">
20
30
  <input class="form-control" type="text" name="rest_args" id="rest_args" required/>
21
31
  </div>
@@ -1,20 +1,11 @@
1
1
  module SidekiqAdhocJob
2
2
  class WorkerClassesLoader
3
-
4
- VALID_QUALIFIED_CLASS_NAME ||= /\A(([A-Z]{1}[a-z]+)(::)*)+\z/
5
- VALID_WORKER_CLASS_NAME ||= /^.+[^(::)]+Worker$/
6
-
7
- StringUtil ||= Utils::String
8
-
9
3
  @_worker_klasses = {}
10
4
 
11
- def self.load(module_names)
12
- module_consts = module_names.map { |name| StringUtil.constantize(name.to_s) }
13
- module_consts.each do |module_const|
14
- module_const.constants.each do |sub_module_const_sym|
15
- load_workers(module_const, sub_module_const_sym, @_worker_klasses)
16
- end
17
- end
5
+ def self.load(module_names, strategy:, load_paths:)
6
+ require_files(load_paths)
7
+ strategy.load
8
+ @_worker_klasses = strategy.worker_klasses
18
9
  end
19
10
 
20
11
  def self.worker_klasses
@@ -25,28 +16,8 @@ module SidekiqAdhocJob
25
16
  @_worker_klasses[path_name]
26
17
  end
27
18
 
28
- def self.load_workers(parent_module_const, module_sym, workers)
29
- qualified_name = module_sym.to_s
30
- return unless VALID_QUALIFIED_CLASS_NAME.match?(qualified_name)
31
-
32
- module_const = begin
33
- StringUtil.constantize(qualified_name)
34
- rescue NameError => _e
35
- qualified_name = [parent_module_const, module_sym].compact.join('::')
36
- begin
37
- StringUtil.constantize(qualified_name)
38
- rescue NameError => _e
39
- nil
40
- end
41
- end
42
- return unless module_const && module_const.is_a?(Class)
43
-
44
- if VALID_WORKER_CLASS_NAME.match?(qualified_name)
45
- path_name = StringUtil.underscore(qualified_name).gsub('/', '_')
46
- workers[path_name] = module_const
47
- return
48
- end
19
+ def self.require_files(load_paths)
20
+ Dir[File.join("", load_paths)].each { |path| require path } unless load_paths.empty?
49
21
  end
50
-
51
22
  end
52
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq_adhoc_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Goh Khoon Hiang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-06 00:00:00.000000000 Z
11
+ date: 2020-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 2.0.1
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 2.0.1
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -98,14 +98,14 @@ dependencies:
98
98
  name: sidekiq
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: 5.2.7
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: 5.2.7
111
111
  description: Trigger jobs adhoc from Sidekiq web admin
@@ -117,6 +117,10 @@ extra_rdoc_files: []
117
117
  files:
118
118
  - lib/sidekiq_adhoc_job.rb
119
119
  - lib/sidekiq_adhoc_job/services/schedule_adhoc_job.rb
120
+ - lib/sidekiq_adhoc_job/strategies/active_job.rb
121
+ - lib/sidekiq_adhoc_job/strategies/default.rb
122
+ - lib/sidekiq_adhoc_job/strategy.rb
123
+ - lib/sidekiq_adhoc_job/utils/class_inspector.rb
120
124
  - lib/sidekiq_adhoc_job/utils/string.rb
121
125
  - lib/sidekiq_adhoc_job/version.rb
122
126
  - lib/sidekiq_adhoc_job/web.rb
@@ -149,7 +153,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
153
  - !ruby/object:Gem::Version
150
154
  version: '0'
151
155
  requirements: []
152
- rubygems_version: 3.0.3
156
+ rubyforge_project:
157
+ rubygems_version: 2.7.7
153
158
  signing_key:
154
159
  specification_version: 4
155
160
  summary: Trigger jobs adhoc from Sidekiq web admin