sidekiq-tasks 0.1.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/.rspec +3 -0
- data/.rubocop.yml +59 -0
- data/.simplecov +16 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +178 -0
- data/Rakefile +12 -0
- data/docs/task.png +0 -0
- data/lib/sidekiq/sidekiq-tasks.rb +1 -0
- data/lib/sidekiq/tasks/config.rb +47 -0
- data/lib/sidekiq/tasks/errors.rb +12 -0
- data/lib/sidekiq/tasks/job.rb +19 -0
- data/lib/sidekiq/tasks/set.rb +57 -0
- data/lib/sidekiq/tasks/storage.rb +85 -0
- data/lib/sidekiq/tasks/strategies/base.rb +91 -0
- data/lib/sidekiq/tasks/strategies/rake_task.rb +26 -0
- data/lib/sidekiq/tasks/strategies/rules/base.rb +19 -0
- data/lib/sidekiq/tasks/strategies/rules/enable_with_comment.rb +29 -0
- data/lib/sidekiq/tasks/strategies/rules/task_from_lib.rb +13 -0
- data/lib/sidekiq/tasks/strategies/rules.rb +11 -0
- data/lib/sidekiq/tasks/strategies.rb +10 -0
- data/lib/sidekiq/tasks/task.rb +42 -0
- data/lib/sidekiq/tasks/task_metadata.rb +27 -0
- data/lib/sidekiq/tasks/validations.rb +37 -0
- data/lib/sidekiq/tasks/version.rb +7 -0
- data/lib/sidekiq/tasks/web/extension.rb +45 -0
- data/lib/sidekiq/tasks/web/helpers/application_helper.rb +17 -0
- data/lib/sidekiq/tasks/web/helpers/task_helper.rb +29 -0
- data/lib/sidekiq/tasks/web/locales/en.yml +19 -0
- data/lib/sidekiq/tasks/web/locales/fr.yml +19 -0
- data/lib/sidekiq/tasks/web/params.rb +44 -0
- data/lib/sidekiq/tasks/web/search.rb +53 -0
- data/lib/sidekiq/tasks/web/views/_pagination.html.erb +25 -0
- data/lib/sidekiq/tasks/web/views/_task.html.erb +84 -0
- data/lib/sidekiq/tasks/web/views/tasks.html.erb +53 -0
- data/lib/sidekiq/tasks/web.rb +18 -0
- data/lib/sidekiq/tasks.rb +37 -0
- data/sig/sidekiq/tasks.rbs +6 -0
- metadata +283 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Strategies
|
4
|
+
class Base
|
5
|
+
include Sidekiq::Tasks::Validations
|
6
|
+
|
7
|
+
# A set of rules to fetch tasks.
|
8
|
+
#
|
9
|
+
# @return [Array<Sidekiq::Tasks::Strategies::Rules::Base>]
|
10
|
+
# @see Sidekiq::Tasks::Strategies::Base#filtered_tasks
|
11
|
+
attr_reader :rules
|
12
|
+
|
13
|
+
# Initializes a strategy with the given rules.
|
14
|
+
#
|
15
|
+
# @param rules [Array<Sidekiq::Tasks::Strategies::Rules::Base>] List of rule instances to be applied.
|
16
|
+
# @raise [Sidekiq::Tasks::ArgumentError] If the rules are not valid instances.
|
17
|
+
def initialize(rules: [])
|
18
|
+
@rules = rules
|
19
|
+
|
20
|
+
validate_array_classes!(rules, [Sidekiq::Tasks::Strategies::Rules::Base], "rules")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the name of the strategy.
|
24
|
+
#
|
25
|
+
# @return [String] The name of the class without module namespaces.
|
26
|
+
def name
|
27
|
+
self.class.name.split("::").last
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns all the raw tasks that should be filtered.
|
31
|
+
#
|
32
|
+
# @abstract Subclasses must implement this method.
|
33
|
+
# @return [Array] A list of tasks to be filtered.
|
34
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
35
|
+
def load_tasks
|
36
|
+
raise NotImplementedError, "Strategy must implement #load_tasks"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Executes a task with the given parameters.
|
40
|
+
#
|
41
|
+
# @note Consider accepting a `Sidekiq::Tasks::Task` instead of a task name.
|
42
|
+
#
|
43
|
+
# @param name [String] The name of the task to execute.
|
44
|
+
# @param args [Hash, NilClass] Arguments to pass to the task.
|
45
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
46
|
+
def execute_task(_name, _args = nil)
|
47
|
+
raise NotImplementedError, "Strategy must implement #execute_task"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Enqueues a task with the given parameters and returns the JID.
|
51
|
+
#
|
52
|
+
# @param name [String] The name of the task to enqueue.
|
53
|
+
# @param params [Hash] Parameters to pass to the task.
|
54
|
+
# @return [String] The JID of the sidekiq job that will execute the task.
|
55
|
+
def enqueue_task(name, params = {})
|
56
|
+
Sidekiq::Tasks::Job.perform_async(name, params.to_json)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns all the tasks that should be executed.
|
60
|
+
#
|
61
|
+
# @return [Array<Sidekiq::Tasks::Task>]
|
62
|
+
def tasks
|
63
|
+
filtered_tasks = load_tasks.select { |task| respects_rules?(task) }
|
64
|
+
|
65
|
+
filtered_tasks.map { |task| Sidekiq::Tasks::Task.new(metadata: build_task_metadata(task), strategy: self) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Factory method to build the metadata for a task.
|
69
|
+
#
|
70
|
+
# @abstract Subclasses must implement this method.
|
71
|
+
# @param task [Object] The task to build the metadata for.
|
72
|
+
# @return [Sidekiq::Tasks::TaskMetadata] The metadata for the task.
|
73
|
+
def build_task_metadata(_task)
|
74
|
+
raise NotImplementedError, "Strategy must implement #build_task_metadata"
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Checks if a task respects all the defined rules.
|
80
|
+
#
|
81
|
+
# @param task [Sidekiq::Tasks::Task] The task to validate against the rules.
|
82
|
+
# @return [Boolean] `true` if the task respects all rules, `false` otherwise.
|
83
|
+
def respects_rules?(task)
|
84
|
+
rules.all? do |rule|
|
85
|
+
rule.respected?(task)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Strategies
|
4
|
+
class RakeTask < Base
|
5
|
+
def load_tasks
|
6
|
+
Rake::TaskManager.record_task_metadata = true
|
7
|
+
Rake.application.load_rakefile
|
8
|
+
Rake::Task.tasks
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_task_metadata(task)
|
12
|
+
Sidekiq::Tasks::TaskMetadata.new(
|
13
|
+
name: task.name,
|
14
|
+
desc: task.full_comment,
|
15
|
+
file: task.locations.first.split(":").first,
|
16
|
+
args: task.arg_names
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute_task(name, args = nil)
|
21
|
+
Rake::Task[name].execute(args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Strategies
|
4
|
+
module Rules
|
5
|
+
class Base
|
6
|
+
# Checks if the given task respects the rule
|
7
|
+
#
|
8
|
+
# @abstract Subclasses must implement this method.
|
9
|
+
# @param task [Sidekiq::Tasks::Task] The task to validate.
|
10
|
+
# @return [Boolean] `true` if the task respects the rule, `false` otherwise.
|
11
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
12
|
+
def respected?(_task)
|
13
|
+
raise NotImplementedError, "Rule must implement #respected?"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Strategies
|
4
|
+
module Rules
|
5
|
+
class EnableWithComment < Base
|
6
|
+
MAGIC_COMMENT_REGEX = /sidekiq-tasks:enable/
|
7
|
+
|
8
|
+
def respected?(task)
|
9
|
+
file, start_line = task.locations.first.split(":")
|
10
|
+
start_line_counting_desc = start_line.to_i > 2 ? start_line.to_i - 3 : 0
|
11
|
+
lines = File.read(file).split("\n")[start_line_counting_desc..start_line_counting_desc + 1].reverse
|
12
|
+
|
13
|
+
valid_magic_comment_line?(lines)
|
14
|
+
rescue Errno::ENOENT
|
15
|
+
raise ArgumentError, "File '#{file}' not found"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def valid_magic_comment_line?(lines)
|
21
|
+
return false if lines.first.match?(/namespace/)
|
22
|
+
|
23
|
+
lines.any? { |line| line.strip.match?(MAGIC_COMMENT_REGEX) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Strategies
|
4
|
+
module Rules
|
5
|
+
autoload :Base, "sidekiq/tasks/strategies/rules/base"
|
6
|
+
autoload :TaskFromLib, "sidekiq/tasks/strategies/rules/task_from_lib"
|
7
|
+
autoload :EnableWithComment, "sidekiq/tasks/strategies/rules/enable_with_comment"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Tasks
|
5
|
+
class Task
|
6
|
+
extend Forwardable
|
7
|
+
include Sidekiq::Tasks::Validations
|
8
|
+
|
9
|
+
def_delegators :metadata, :name, :desc, :file, :args
|
10
|
+
def_delegators :storage, :last_enqueue_at, :last_execution_at, :history
|
11
|
+
|
12
|
+
attr_reader :metadata, :strategy
|
13
|
+
|
14
|
+
# @param metadata [Sidekiq::Tasks::TaskMetadata] The metadata for the task.
|
15
|
+
# @param strategy [Sidekiq::Tasks::Strategies::Base] The strategy to use to execute the task.
|
16
|
+
# @raise [Sidekiq::Tasks::ArgumentError] If the metadata or strategy are not valid instances.
|
17
|
+
def initialize(metadata:, strategy:)
|
18
|
+
@metadata = metadata
|
19
|
+
@strategy = strategy
|
20
|
+
|
21
|
+
validate_class!(metadata, [Sidekiq::Tasks::TaskMetadata], "metadata")
|
22
|
+
validate_class!(strategy, [Sidekiq::Tasks::Strategies::Base], "strategy")
|
23
|
+
end
|
24
|
+
|
25
|
+
def enqueue(params = {})
|
26
|
+
jid = strategy.enqueue_task(name, params)
|
27
|
+
|
28
|
+
storage.store_enqueue(jid, params)
|
29
|
+
end
|
30
|
+
|
31
|
+
def execute(params = {}, jid: nil)
|
32
|
+
strategy.execute_task(name, params)
|
33
|
+
|
34
|
+
storage.store_execution(jid)
|
35
|
+
end
|
36
|
+
|
37
|
+
def storage
|
38
|
+
@_storage ||= Sidekiq::Tasks::Storage.new(name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
class TaskMetadata
|
4
|
+
include Sidekiq::Tasks::Validations
|
5
|
+
|
6
|
+
attr_reader :name, :desc, :file, :args
|
7
|
+
|
8
|
+
def initialize(name:, file:, desc: "", args: [])
|
9
|
+
@name = name
|
10
|
+
@file = file
|
11
|
+
@desc = desc
|
12
|
+
@args = args
|
13
|
+
|
14
|
+
validate_params!
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def validate_params!
|
20
|
+
validate_class!(name, [String, Symbol], "name")
|
21
|
+
validate_class!(file, [String, NilClass], "file")
|
22
|
+
validate_class!(desc, [String, NilClass], "desc")
|
23
|
+
validate_array_classes!(args, [String, Symbol], "args")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Validations
|
4
|
+
def validate_class!(object, classes, name = nil)
|
5
|
+
return if classes.any? { |klass| object.is_a?(klass) }
|
6
|
+
|
7
|
+
expected_classes = classes.map(&:name).join(" or ")
|
8
|
+
name ||= object
|
9
|
+
|
10
|
+
raise Sidekiq::Tasks::ArgumentError,
|
11
|
+
"'#{name}' must be an instance of #{expected_classes} but received #{object.class}"
|
12
|
+
end
|
13
|
+
module_function :validate_class!
|
14
|
+
|
15
|
+
def validate_array_classes!(objects, classes, name = nil)
|
16
|
+
validate_class!(objects, [Array], name)
|
17
|
+
|
18
|
+
objects.each { |object| validate_class!(object, classes) }
|
19
|
+
end
|
20
|
+
module_function :validate_array_classes!
|
21
|
+
|
22
|
+
def validate_hash_option!(options, key, classes = [])
|
23
|
+
validate_class!(options, [Hash])
|
24
|
+
validate_class!(options[key], classes, key)
|
25
|
+
end
|
26
|
+
module_function :validate_hash_option!
|
27
|
+
|
28
|
+
def validate_expected_values!(value, expected_values, name = nil)
|
29
|
+
return if expected_values.any? { |expected_value| value == expected_value }
|
30
|
+
|
31
|
+
raise Sidekiq::Tasks::ArgumentError,
|
32
|
+
"'#{name}' must be one of #{expected_values.map(&:inspect).join(" or ")} but received #{value.inspect}"
|
33
|
+
end
|
34
|
+
module_function :validate_expected_values!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Web
|
4
|
+
class Extension
|
5
|
+
LOCALES_PATH = File.expand_path("../web/locales", __dir__).freeze
|
6
|
+
|
7
|
+
def self.registered(app)
|
8
|
+
app.settings.locales << File.join(LOCALES_PATH)
|
9
|
+
|
10
|
+
app.helpers do
|
11
|
+
include Sidekiq::Tasks::Web::Helpers::ApplicationHelper
|
12
|
+
include Sidekiq::Tasks::Web::Helpers::TaskHelper
|
13
|
+
end
|
14
|
+
|
15
|
+
app.get "/tasks" do
|
16
|
+
@search = Sidekiq::Tasks::Web::Search.new(params)
|
17
|
+
|
18
|
+
erb(read_view(:tasks), locals: {search: @search})
|
19
|
+
end
|
20
|
+
|
21
|
+
app.get "/tasks/:name" do
|
22
|
+
@task = find_task!(params["name"])
|
23
|
+
|
24
|
+
erb(read_view(:_task), locals: {task: @task})
|
25
|
+
rescue Sidekiq::Tasks::NotFoundError
|
26
|
+
throw :halt, [404, {Rack::CONTENT_TYPE => "text/plain"}, ["Task not found"]]
|
27
|
+
end
|
28
|
+
|
29
|
+
app.post "/tasks/:name/enqueue" do
|
30
|
+
task = find_task!(params["name"])
|
31
|
+
args = Sidekiq::Tasks::Web::Params.new(task, params["args"]).permit!
|
32
|
+
|
33
|
+
task.enqueue(args)
|
34
|
+
|
35
|
+
redirect(task_url(root_path, task))
|
36
|
+
rescue Sidekiq::Tasks::ArgumentError => e
|
37
|
+
throw :halt, [400, {Rack::CONTENT_TYPE => "text/plain"}, [e.message]]
|
38
|
+
rescue Sidekiq::Tasks::NotFoundError
|
39
|
+
throw :halt, [404, {Rack::CONTENT_TYPE => "text/plain"}, ["Task not found"]]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Web
|
4
|
+
module Helpers
|
5
|
+
module ApplicationHelper
|
6
|
+
extend self
|
7
|
+
|
8
|
+
VIEW_PATH = File.expand_path("../../web/views", __dir__).freeze
|
9
|
+
|
10
|
+
def read_view(name)
|
11
|
+
File.read(File.join(VIEW_PATH, "#{name}.html.erb"))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Web
|
4
|
+
module Helpers
|
5
|
+
module TaskHelper
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def parameterize_task_name(task_name)
|
9
|
+
task_name.gsub(":", "-")
|
10
|
+
end
|
11
|
+
|
12
|
+
def unparameterize_task_name(task_name)
|
13
|
+
task_name.gsub("-", ":")
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_task!(parameterized_name)
|
17
|
+
name = unparameterize_task_name(parameterized_name)
|
18
|
+
|
19
|
+
Sidekiq::Tasks.tasks.find_by!(name: name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def task_url(root_path, task)
|
23
|
+
"#{root_path}tasks/#{parameterize_task_name(task.name)}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
en:
|
2
|
+
prev: "Prev"
|
3
|
+
next: "Next"
|
4
|
+
tasks: "Tasks"
|
5
|
+
filter: "Filter"
|
6
|
+
no_tasks: "No tasks found"
|
7
|
+
name: "Name"
|
8
|
+
last_enqueued: "Last enqueued"
|
9
|
+
history: "History"
|
10
|
+
no_history: "No history"
|
11
|
+
jid: "JID"
|
12
|
+
args: "Arguments"
|
13
|
+
enqueued_at: "Enqueued at"
|
14
|
+
executed_at: "Executed at"
|
15
|
+
task: "Task"
|
16
|
+
desc: "Description"
|
17
|
+
strategy: "Strategy"
|
18
|
+
run_task: "Run task"
|
19
|
+
enqueue: "Enqueue"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
fr:
|
2
|
+
prev: "Précédent"
|
3
|
+
next: "Suivant"
|
4
|
+
tasks: "Tâches"
|
5
|
+
filter: "Filtrer"
|
6
|
+
no_tasks: "Aucune tâche trouvée"
|
7
|
+
name: "Nom"
|
8
|
+
last_enqueued: "Dernière mise en file d'attente"
|
9
|
+
history: "Historique"
|
10
|
+
no_history: "Aucun historique"
|
11
|
+
jid: "JID"
|
12
|
+
args: "Arguments"
|
13
|
+
enqueued_at: "Mise en file d'attente le"
|
14
|
+
executed_at: "Exécuté le"
|
15
|
+
task: "Tâche"
|
16
|
+
desc: "Description"
|
17
|
+
strategy: "Stratégie"
|
18
|
+
run_task: "Exécuter la tâche"
|
19
|
+
enqueue: "Mettre en file d'attente"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Web
|
4
|
+
class Params
|
5
|
+
attr_reader :task, :params
|
6
|
+
|
7
|
+
# @param task [Sidekiq::Tasks::Task] The task to validate the params against.
|
8
|
+
# @param params [Hash] The params to validate.
|
9
|
+
def initialize(task, params)
|
10
|
+
@task = task
|
11
|
+
@params = params
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the permitted params.
|
15
|
+
#
|
16
|
+
# @return [Hash] The permitted params.
|
17
|
+
# @raise [Sidekiq::Tasks::ArgumentError] If the params are not NilClass or Hash.
|
18
|
+
def permit!
|
19
|
+
case params
|
20
|
+
when NilClass then {}
|
21
|
+
when Hash then permit_hash!
|
22
|
+
else
|
23
|
+
raise Sidekiq::Tasks::ArgumentError, "Invalid parameters: #{params.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Validates and returns the permitted params as a hash.
|
30
|
+
#
|
31
|
+
# @return [Hash] The permitted params as a hash.
|
32
|
+
# @raise [Sidekiq::Tasks::ArgumentError] If given params does not match the task args.
|
33
|
+
def permit_hash!
|
34
|
+
permitted_keys = task.args.map(&:to_s)
|
35
|
+
invalid_keys = params.keys - permitted_keys
|
36
|
+
|
37
|
+
raise Sidekiq::Tasks::ArgumentError, "Invalid parameters: #{invalid_keys.join(", ")}" if invalid_keys.any?
|
38
|
+
|
39
|
+
params.slice(*permitted_keys)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Tasks
|
3
|
+
module Web
|
4
|
+
class Search
|
5
|
+
DEFAULT_COUNT = 25
|
6
|
+
|
7
|
+
def self.count_options
|
8
|
+
(1..4).map { |index| index * DEFAULT_COUNT }
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :params
|
12
|
+
|
13
|
+
def initialize(params)
|
14
|
+
@params = params
|
15
|
+
end
|
16
|
+
|
17
|
+
def tasks
|
18
|
+
@_tasks ||= filtered_collection.sort_by(&:file).slice(offset, count) || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def filtered_collection
|
22
|
+
@_filtered_collection ||= Sidekiq::Tasks.tasks.where(name: filter)
|
23
|
+
end
|
24
|
+
|
25
|
+
def filter
|
26
|
+
request_filter = params[:filter]
|
27
|
+
|
28
|
+
["", nil].include?(request_filter) ? nil : request_filter
|
29
|
+
end
|
30
|
+
|
31
|
+
def count
|
32
|
+
requested_count = params[:count].to_i
|
33
|
+
|
34
|
+
requested_count.positive? ? requested_count : DEFAULT_COUNT
|
35
|
+
end
|
36
|
+
|
37
|
+
def page
|
38
|
+
requested_page = params[:page].to_i
|
39
|
+
|
40
|
+
requested_page.positive? ? requested_page : 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def total_pages
|
44
|
+
(filtered_collection.size.to_f / count).ceil
|
45
|
+
end
|
46
|
+
|
47
|
+
def offset
|
48
|
+
(page - 1) * count
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<% if search.total_pages > 1 %>
|
2
|
+
<ul class="pagination pull-right">
|
3
|
+
<% if search.page > 1 %>
|
4
|
+
<li>
|
5
|
+
<a href="<%= "#{root_path}tasks?filter=#{ERB::Util.url_encode(search.filter)}&count=#{search.count}&page=#{search.page - 1}" %>">
|
6
|
+
<%= t("prev") %>
|
7
|
+
</a>
|
8
|
+
</li>
|
9
|
+
<% end %>
|
10
|
+
|
11
|
+
<% (0..(search.total_pages - 1)).each.with_index(1) do |_page, index| %>
|
12
|
+
<li class="<%= 'active' if index == search.page %>">
|
13
|
+
<a href="<%= "#{root_path}tasks?filter=#{ERB::Util.url_encode(search.filter)}&count=#{search.count}&page=#{index}" %>"><%= index %></a>
|
14
|
+
</li>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<% if search.tasks.any? && search.tasks.size == search.count %>
|
18
|
+
<li>
|
19
|
+
<a href="<%= "#{root_path}tasks?filter=#{ERB::Util.url_encode(search.filter)}&count=#{search.count}&page=#{search.page + 1}" %>">
|
20
|
+
<%= t("next") %>
|
21
|
+
</a>
|
22
|
+
</li>
|
23
|
+
<% end %>
|
24
|
+
</ul>
|
25
|
+
<% end %>
|
@@ -0,0 +1,84 @@
|
|
1
|
+
<header class="row">
|
2
|
+
<div class="span col-sm-5 pull-left">
|
3
|
+
<h1><%= t("task") %></h1>
|
4
|
+
</div>
|
5
|
+
</header>
|
6
|
+
|
7
|
+
<table class="table table-bordered table-striped">
|
8
|
+
<tbody>
|
9
|
+
<tr>
|
10
|
+
<th><%= t("name") %></th>
|
11
|
+
<td><%= task.name %></td>
|
12
|
+
</tr>
|
13
|
+
<tr>
|
14
|
+
<th><%= t("desc") %></th>
|
15
|
+
<td><%= task.desc %></td>
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<th><%= t("strategy") %></th>
|
19
|
+
<td><%= task.strategy.name %></td>
|
20
|
+
</tr>
|
21
|
+
<tr>
|
22
|
+
<th><%= t("last_enqueued") %></th>
|
23
|
+
<td><%= task.last_enqueue_at ? relative_time(task.last_enqueue_at) : "-" %></td>
|
24
|
+
</tr>
|
25
|
+
</tbody>
|
26
|
+
</table>
|
27
|
+
|
28
|
+
<header class="row">
|
29
|
+
<div class="col-sm-12">
|
30
|
+
<h2><%= t("history") %></h2>
|
31
|
+
</div>
|
32
|
+
</header>
|
33
|
+
|
34
|
+
<% if task.history.empty? %>
|
35
|
+
<p><%= t("no_history") %></p>
|
36
|
+
<% else %>
|
37
|
+
<table class="table table-hover table-bordered table-striped">
|
38
|
+
<thead>
|
39
|
+
<tr>
|
40
|
+
<th><%= t("jid") %></th>
|
41
|
+
<th><%= t("args") %></th>
|
42
|
+
<th><%= t("enqueued_at") %></th>
|
43
|
+
<th><%= t("executed_at") %></th>
|
44
|
+
</tr>
|
45
|
+
</thead>
|
46
|
+
<tbody>
|
47
|
+
<% task.history.each do |jid_history| %>
|
48
|
+
<tr>
|
49
|
+
<td><%= jid_history["jid"] %></td>
|
50
|
+
<td><%= jid_history["args"] %></td>
|
51
|
+
<td><%= jid_history["enqueued_at"] ? relative_time(jid_history["enqueued_at"]) : "-" %></td>
|
52
|
+
<td><%= jid_history["executed_at"] ? relative_time(jid_history["executed_at"]) : "-" %></td>
|
53
|
+
</tr>
|
54
|
+
<% end %>
|
55
|
+
</tbody>
|
56
|
+
</table>
|
57
|
+
<% end %>
|
58
|
+
|
59
|
+
<header class="row">
|
60
|
+
<div class="col-sm-12">
|
61
|
+
<h2><%= t("run_task") %></h2>
|
62
|
+
</div>
|
63
|
+
</header>
|
64
|
+
|
65
|
+
<form action="<%= task_url(root_path, task) %>/enqueue" method="post">
|
66
|
+
<%= csrf_tag %>
|
67
|
+
|
68
|
+
<div class="container">
|
69
|
+
<div class="row">
|
70
|
+
<% task.args.each do |arg| %>
|
71
|
+
<div class="col-md-6 mb-3">
|
72
|
+
<div class="form-group">
|
73
|
+
<label for="<%= arg %>" class="form-label"><%= arg %></label>
|
74
|
+
<input type="text" class="form-control" name="args[<%= arg %>]" id="<%= arg %>" />
|
75
|
+
</div>
|
76
|
+
</div>
|
77
|
+
<% end %>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
|
81
|
+
<button type="submit" class="btn btn-primary">
|
82
|
+
<%= t("enqueue") %>
|
83
|
+
</button>
|
84
|
+
</form>
|