sidekiq_adhoc_job 0.1.3 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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