sidekiq-tasks 0.1.7 → 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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +41 -0
- data/docs/custom_storage.md +95 -0
- data/lib/sidekiq/tasks/config.rb +30 -2
- data/lib/sidekiq/tasks/set.rb +1 -1
- data/lib/sidekiq/tasks/storage/base.rb +73 -0
- data/lib/sidekiq/tasks/storage/redis.rb +89 -0
- data/lib/sidekiq/tasks/storage.rb +3 -96
- data/lib/sidekiq/tasks/task.rb +4 -4
- data/lib/sidekiq/tasks/validations.rb +8 -0
- data/lib/sidekiq/tasks/version.rb +1 -1
- data/lib/sidekiq/tasks/web/extension.rb +21 -3
- data/lib/sidekiq/tasks/web/helpers/application_helper.rb +6 -0
- data/lib/sidekiq/tasks/web/helpers/pagination_helper.rb +15 -8
- data/lib/sidekiq/tasks/web/helpers/sort_helper.rb +26 -0
- data/lib/sidekiq/tasks/web/search.rb +44 -1
- data/lib/sidekiq/tasks/web.rb +1 -0
- data/lib/sidekiq/tasks.rb +1 -1
- data/web/assets/tasks/css/components/tables.css +34 -0
- data/web/locales/en.yml +1 -0
- data/web/locales/fr.yml +1 -0
- data/web/views/_pagination.erb +4 -4
- data/web/views/task.erb +33 -6
- data/web/views/tasks.erb +38 -5
- metadata +11 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8fee03d24fdcc9755f8fadb9915b39127fac89ba1f4447a7d822998b9a64c102
|
|
4
|
+
data.tar.gz: 8a6c5940eae17706522c7f584c392779a0b2c513848fa49cab8206c2a5af5473
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b85eb95f150f99c0de282d0ac6fb1356a984f0cdc7d5a485baf1a9fdb7963346156a9e4c224d1cd22476002efd34bb8ed18bbf1252d762cbd25c8b41623557b4
|
|
7
|
+
data.tar.gz: 51ca1179d3232c84139c8ef380290aa5e9bd8f3b97b21e92a54165fcee0376a1a42b207ec9029510f086b35e2ec69aa091685f89d343a3eb38c31296d8600709
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### [1.0.0] - 2026-03-13
|
|
4
|
+
|
|
5
|
+
- Add configurable `storage` option with support for custom backends (default: Redis).
|
|
6
|
+
- Add configurable `current_user` option to track who enqueued each task.
|
|
7
|
+
- Add configurable `history_limit` option (default: 10).
|
|
8
|
+
- Add sortable columns (`name`, `last_enqueued`) in the tasks list.
|
|
9
|
+
|
|
10
|
+
### [0.1.8] - 2026-03-06
|
|
11
|
+
|
|
12
|
+
- Replace deprecated `webdrivers` gem with `selenium-webdriver`.
|
|
13
|
+
- Avoid duplicate CI runs on pull request branches.
|
|
14
|
+
- Fix `retry` option validation to accept integer values.
|
|
15
|
+
- Add `retry_for` option support (Sidekiq 7.1.3+).
|
|
16
|
+
- Fix `find_by` to use exact name matching instead of fuzzy matching.
|
|
17
|
+
- Disable live poll on tasks pages to prevent form state loss (Sidekiq >= 7.0.1).
|
|
18
|
+
- Escape filter parameter in tasks view to prevent HTML injection.
|
|
19
|
+
|
|
3
20
|
### [0.1.7] - 2025-07-27
|
|
4
21
|
|
|
5
22
|
- Support multiline description in task view.
|
data/README.md
CHANGED
|
@@ -10,6 +10,11 @@ Sidekiq-Tasks extends Sidekiq by providing an interface to enqueue tasks directl
|
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Ruby >= 3.0
|
|
16
|
+
- Sidekiq >= 6.5 (compatible with 6.5, 7.x, and 8.x)
|
|
17
|
+
|
|
13
18
|
## Installation
|
|
14
19
|
|
|
15
20
|
```bash
|
|
@@ -190,6 +195,8 @@ Sidekiq::Tasks.configure do |config|
|
|
|
190
195
|
end
|
|
191
196
|
```
|
|
192
197
|
|
|
198
|
+
All standard [Sidekiq job options](https://github.com/sidekiq/sidekiq/wiki/Advanced-Options#jobs) are supported.
|
|
199
|
+
|
|
193
200
|
You can also override the `enqueue_task` method to implement your own enqueuing logic for your strategy:
|
|
194
201
|
|
|
195
202
|
```ruby
|
|
@@ -218,6 +225,40 @@ end
|
|
|
218
225
|
>[!NOTE]
|
|
219
226
|
> The `Tasks` button in the header will still be displayed regardless of the value of `authorization`.
|
|
220
227
|
|
|
228
|
+
## Execution history
|
|
229
|
+
|
|
230
|
+
Each task keeps a history of its last executions (default: 10, stored in Redis). The task detail page displays for each execution:
|
|
231
|
+
|
|
232
|
+
- The enqueue, start, and finish timestamps
|
|
233
|
+
- The arguments passed
|
|
234
|
+
- The error message if the execution failed
|
|
235
|
+
|
|
236
|
+
You can configure the history limit:
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
Sidekiq::Tasks.configure do |config|
|
|
240
|
+
config.history_limit = 25
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
You can also use a custom storage backend. See the [Custom Storage Guide](docs/custom_storage.md) for more details.
|
|
245
|
+
|
|
246
|
+
## Current user
|
|
247
|
+
|
|
248
|
+
You can configure a `current_user` proc to track who enqueued each task.
|
|
249
|
+
The proc receives the Rack `env` and should return a Hash identifying the user:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
Sidekiq::Tasks.configure do |config|
|
|
253
|
+
config.current_user = ->(env) do
|
|
254
|
+
user = env["warden"].user(:admin_user)
|
|
255
|
+
{id: user.id, email: user.email}
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
When configured, an "Enqueued by" column appears in the task history table.
|
|
261
|
+
|
|
221
262
|
## Development
|
|
222
263
|
|
|
223
264
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Custom Storage Guide
|
|
2
|
+
|
|
3
|
+
This guide walks you through implementing a custom storage backend using ActiveRecord in a Rails application.
|
|
4
|
+
|
|
5
|
+
## Step 1: Generate the model and run the migration
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
rails generate model TaskExecution \
|
|
9
|
+
task_name:string:index \
|
|
10
|
+
jid:string:index \
|
|
11
|
+
args:jsonb \
|
|
12
|
+
enqueued_at:datetime \
|
|
13
|
+
executed_at:datetime \
|
|
14
|
+
finished_at:datetime \
|
|
15
|
+
error:string \
|
|
16
|
+
user:jsonb
|
|
17
|
+
|
|
18
|
+
rails db:migrate
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Step 2: Configure the initializer
|
|
22
|
+
|
|
23
|
+
In `config/initializers/sidekiq_tasks.rb`:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
class ActiveRecordStorage < Sidekiq::Tasks::Storage::Base
|
|
27
|
+
def last_enqueue_at
|
|
28
|
+
TaskExecution.where(task_name: task_name).order(enqueued_at: :desc).pick(:enqueued_at)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def history
|
|
32
|
+
TaskExecution
|
|
33
|
+
.where(task_name: task_name)
|
|
34
|
+
.order(enqueued_at: :desc)
|
|
35
|
+
.limit(history_limit)
|
|
36
|
+
.select(:jid, :task_name, :args, :enqueued_at, :executed_at, :finished_at, :error, :user)
|
|
37
|
+
.map(&:attributes)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def store_enqueue(jid, args, user: nil)
|
|
41
|
+
TaskExecution.create!(
|
|
42
|
+
task_name: task_name,
|
|
43
|
+
jid: jid,
|
|
44
|
+
args: args,
|
|
45
|
+
enqueued_at: Time.now,
|
|
46
|
+
user: user
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def store_execution(jid, time_key)
|
|
51
|
+
TaskExecution.find_by(jid: jid)&.update!(time_key => Time.now)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def store_execution_error(jid, error)
|
|
55
|
+
message = truncate_message("#{error.class}: #{error.message}", ERROR_MESSAGE_MAX_LENGTH)
|
|
56
|
+
TaskExecution.find_by(jid: jid)&.update!(error: message)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
Sidekiq::Tasks.configure do |config|
|
|
61
|
+
config.storage = ActiveRecordStorage
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> [!NOTE]
|
|
66
|
+
> The `history_limit` config is passed to each storage instance. The default Redis storage uses it to trim old entries. Custom storage implementations receive it via the `history_limit` accessor and can use it as needed (e.g. as a SQL `LIMIT`) or ignore it entirely.
|
|
67
|
+
|
|
68
|
+
## History entry format
|
|
69
|
+
|
|
70
|
+
The `history` method must return an array of hashes with the following keys:
|
|
71
|
+
|
|
72
|
+
| Key | Type | Description |
|
|
73
|
+
|-----------------|---------------|--------------------------------------|
|
|
74
|
+
| `"jid"` | String | The Sidekiq job ID |
|
|
75
|
+
| `"task_name"` | String | The task name |
|
|
76
|
+
| `"args"` | Hash | The arguments passed to the task |
|
|
77
|
+
| `"enqueued_at"` | Time | When the task was enqueued |
|
|
78
|
+
| `"executed_at"` | Time \| nil | When the task started executing |
|
|
79
|
+
| `"finished_at"` | Time \| nil | When the task finished executing |
|
|
80
|
+
| `"error"` | String \| nil | Error message if execution failed |
|
|
81
|
+
| `"user"` | Hash \| nil | User who enqueued the task |
|
|
82
|
+
|
|
83
|
+
## Migrating from Redis
|
|
84
|
+
|
|
85
|
+
After switching to a custom storage, you can clean up the old Redis history:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
redis-cli --scan --pattern "task:*" | xargs redis-cli del
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or from a Rails console:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
Sidekiq.redis { |conn| conn.keys("task:*").each { |key| conn.del(key) } }
|
|
95
|
+
```
|
data/lib/sidekiq/tasks/config.rb
CHANGED
|
@@ -15,21 +15,29 @@ module Sidekiq
|
|
|
15
15
|
),
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
|
+
DEFAULT_STORAGE = Sidekiq::Tasks::Storage::Redis
|
|
19
|
+
|
|
20
|
+
DEFAULT_HISTORY_LIMIT = 10
|
|
21
|
+
|
|
18
22
|
include Sidekiq::Tasks::Validations
|
|
19
23
|
|
|
20
|
-
attr_reader :strategies, :sidekiq_options, :authorization
|
|
24
|
+
attr_reader :strategies, :sidekiq_options, :authorization, :history_limit, :current_user, :storage
|
|
21
25
|
|
|
22
26
|
def initialize
|
|
23
27
|
@sidekiq_options = DEFAULT_SIDEKIQ_OPTIONS
|
|
24
28
|
@strategies = DEFAULT_STRATEGIES
|
|
29
|
+
@storage = DEFAULT_STORAGE
|
|
25
30
|
@authorization = ->(_env) { true }
|
|
31
|
+
@history_limit = DEFAULT_HISTORY_LIMIT
|
|
32
|
+
@current_user = nil
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
# @see https://github.com/sidekiq/sidekiq/wiki/Advanced-Options#jobs
|
|
29
36
|
def sidekiq_options=(options)
|
|
30
37
|
validate_class!(options, [Hash], "sidekiq_options")
|
|
31
38
|
validate_hash_option!(options, :queue, [String])
|
|
32
|
-
validate_hash_option!(options, :retry, [TrueClass, FalseClass])
|
|
39
|
+
validate_hash_option!(options, :retry, [NilClass, TrueClass, FalseClass, Integer])
|
|
40
|
+
validate_hash_option!(options, :retry_for, [NilClass, Integer, Float])
|
|
33
41
|
validate_hash_option!(options, :dead, [NilClass, TrueClass, FalseClass])
|
|
34
42
|
validate_hash_option!(options, :backtrace, [NilClass, TrueClass, FalseClass, Integer])
|
|
35
43
|
validate_hash_option!(options, :pool, [NilClass, String])
|
|
@@ -44,11 +52,31 @@ module Sidekiq
|
|
|
44
52
|
@strategies = strategies
|
|
45
53
|
end
|
|
46
54
|
|
|
55
|
+
def storage=(storage_class)
|
|
56
|
+
validate_subclass!(storage_class, Sidekiq::Tasks::Storage::Base, "storage")
|
|
57
|
+
|
|
58
|
+
@storage = storage_class
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def history_limit=(limit)
|
|
62
|
+
validate_class!(limit, [Integer], "history_limit")
|
|
63
|
+
|
|
64
|
+
raise Sidekiq::Tasks::ArgumentError, "'history_limit' must be greater than 0" if limit <= 0
|
|
65
|
+
|
|
66
|
+
@history_limit = limit
|
|
67
|
+
end
|
|
68
|
+
|
|
47
69
|
def authorization=(authorization_proc)
|
|
48
70
|
validate_class!(authorization_proc, [Proc], "authorization")
|
|
49
71
|
|
|
50
72
|
@authorization = authorization_proc
|
|
51
73
|
end
|
|
74
|
+
|
|
75
|
+
def current_user=(current_user_proc)
|
|
76
|
+
validate_class!(current_user_proc, [Proc], "current_user")
|
|
77
|
+
|
|
78
|
+
@current_user = current_user_proc
|
|
79
|
+
end
|
|
52
80
|
end
|
|
53
81
|
end
|
|
54
82
|
end
|
data/lib/sidekiq/tasks/set.rb
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Sidekiq
|
|
2
|
+
module Tasks
|
|
3
|
+
module Storage
|
|
4
|
+
class Base
|
|
5
|
+
ERROR_MESSAGE_MAX_LENGTH = 255
|
|
6
|
+
|
|
7
|
+
attr_reader :task_name, :history_limit
|
|
8
|
+
|
|
9
|
+
def initialize(task_name, history_limit: nil)
|
|
10
|
+
@task_name = task_name
|
|
11
|
+
@history_limit = history_limit
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns the last enqueue time for the task.
|
|
15
|
+
#
|
|
16
|
+
# @abstract Subclasses must implement this method.
|
|
17
|
+
# @return [Time, NilClass] The last enqueue time or nil.
|
|
18
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
|
19
|
+
def last_enqueue_at
|
|
20
|
+
raise NotImplementedError, "Storage must implement #last_enqueue_at"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns the execution history for the task.
|
|
24
|
+
#
|
|
25
|
+
# @abstract Subclasses must implement this method.
|
|
26
|
+
# @return [Array<Hash>] The execution history entries.
|
|
27
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
|
28
|
+
def history
|
|
29
|
+
raise NotImplementedError, "Storage must implement #history"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Stores enqueue information for the task.
|
|
33
|
+
#
|
|
34
|
+
# @abstract Subclasses must implement this method.
|
|
35
|
+
# @param jid [String] The Sidekiq job ID.
|
|
36
|
+
# @param args [Hash] The arguments passed to the task.
|
|
37
|
+
# @param user [Hash, NilClass] The user who enqueued the task.
|
|
38
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
|
39
|
+
def store_enqueue(_jid, _args, user: nil)
|
|
40
|
+
raise NotImplementedError, "Storage must implement #store_enqueue"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Stores execution time for a specific history entry.
|
|
44
|
+
#
|
|
45
|
+
# @abstract Subclasses must implement this method.
|
|
46
|
+
# @param jid [String] The Sidekiq job ID.
|
|
47
|
+
# @param time_key [String] The time key to store (e.g. "executed_at", "finished_at").
|
|
48
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
|
49
|
+
def store_execution(_jid, _time_key)
|
|
50
|
+
raise NotImplementedError, "Storage must implement #store_execution"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Stores an execution error for a specific history entry.
|
|
54
|
+
#
|
|
55
|
+
# @abstract Subclasses must implement this method.
|
|
56
|
+
# @param jid [String] The Sidekiq job ID.
|
|
57
|
+
# @param error [Exception] The error that occurred during execution.
|
|
58
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
|
59
|
+
def store_execution_error(_jid, _error)
|
|
60
|
+
raise NotImplementedError, "Storage must implement #store_execution_error"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
protected
|
|
64
|
+
|
|
65
|
+
def truncate_message(message, max_length)
|
|
66
|
+
return message if message.length <= max_length
|
|
67
|
+
|
|
68
|
+
"#{message[0...(max_length - 3)]}..."
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Sidekiq
|
|
2
|
+
module Tasks
|
|
3
|
+
module Storage
|
|
4
|
+
class Redis < Base
|
|
5
|
+
JID_PREFIX = "task".freeze
|
|
6
|
+
def jid_key
|
|
7
|
+
"#{JID_PREFIX}:#{task_name}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def history_key
|
|
11
|
+
"#{jid_key}:history"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def last_enqueue_at
|
|
15
|
+
stored_time("last_enqueue_at")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def history
|
|
19
|
+
raw_entries = Sidekiq.redis { |conn| conn.lrange(history_key, 0, -1) }
|
|
20
|
+
|
|
21
|
+
return [] unless raw_entries
|
|
22
|
+
|
|
23
|
+
raw_entries.map do |raw|
|
|
24
|
+
entry = Sidekiq.load_json(raw)
|
|
25
|
+
["enqueued_at", "executed_at", "finished_at"].each do |key|
|
|
26
|
+
entry[key] = Time.at(entry[key]) if entry[key]
|
|
27
|
+
end
|
|
28
|
+
entry
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def store_history(jid, task_args, time, user: nil)
|
|
33
|
+
Sidekiq.redis do |conn|
|
|
34
|
+
task_trace = {jid: jid, task_name: task_name, args: task_args, enqueued_at: time.to_f}
|
|
35
|
+
task_trace[:user] = user if user
|
|
36
|
+
conn.lpush(history_key, Sidekiq.dump_json(task_trace))
|
|
37
|
+
conn.ltrim(history_key, 0, history_limit - 1)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def store_enqueue(jid, args, user: nil)
|
|
42
|
+
time = Time.now.to_f
|
|
43
|
+
store_time(time, "last_enqueue_at")
|
|
44
|
+
store_history(jid, args, time, user: user)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def store_execution(jid, time_key)
|
|
48
|
+
update_history_entry(jid) do |entry|
|
|
49
|
+
entry.merge(time_key => Time.now.to_f)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def store_execution_error(jid, error)
|
|
54
|
+
update_history_entry(jid) do |entry|
|
|
55
|
+
error_message = truncate_message("#{error.class}: #{error.message}", ERROR_MESSAGE_MAX_LENGTH)
|
|
56
|
+
entry.merge("error" => error_message)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def store_time(time, time_key)
|
|
63
|
+
Sidekiq.redis { |conn| conn.hset(jid_key, time_key, time.to_f) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def stored_time(time_key)
|
|
67
|
+
timestamp = Sidekiq.redis { |conn| conn.hget(jid_key, time_key) }
|
|
68
|
+
|
|
69
|
+
[nil, ""].include?(timestamp) ? nil : Time.at(timestamp.to_f)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def update_history_entry(jid)
|
|
73
|
+
Sidekiq.redis do |conn|
|
|
74
|
+
entries = conn.lrange(history_key, 0, -1)
|
|
75
|
+
|
|
76
|
+
entries.each_with_index do |raw, index|
|
|
77
|
+
entry = Sidekiq.load_json(raw)
|
|
78
|
+
next unless entry["jid"] == jid
|
|
79
|
+
|
|
80
|
+
updated_entry = yield(entry)
|
|
81
|
+
conn.lset(history_key, index, Sidekiq.dump_json(updated_entry))
|
|
82
|
+
break
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -1,101 +1,8 @@
|
|
|
1
1
|
module Sidekiq
|
|
2
2
|
module Tasks
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
ERROR_MESSAGE_MAX_LENGTH = 255
|
|
7
|
-
|
|
8
|
-
attr_reader :task_name
|
|
9
|
-
|
|
10
|
-
def initialize(task_name)
|
|
11
|
-
@task_name = task_name
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def jid_key
|
|
15
|
-
"#{JID_PREFIX}:#{task_name}"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def history_key
|
|
19
|
-
"#{jid_key}:history"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def last_enqueue_at
|
|
23
|
-
stored_time("last_enqueue_at")
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def history
|
|
27
|
-
raw_entries = Sidekiq.redis { |conn| conn.lrange(history_key, 0, -1) }
|
|
28
|
-
|
|
29
|
-
return [] unless raw_entries
|
|
30
|
-
|
|
31
|
-
raw_entries.map do |raw|
|
|
32
|
-
entry = Sidekiq.load_json(raw)
|
|
33
|
-
%w[enqueued_at executed_at finished_at].each do |key|
|
|
34
|
-
entry[key] = Time.at(entry[key]) if entry[key]
|
|
35
|
-
end
|
|
36
|
-
entry
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def store_history(jid, task_args, time)
|
|
41
|
-
Sidekiq.redis do |conn|
|
|
42
|
-
task_trace = {jid: jid, name: task_name, args: task_args, enqueued_at: time.to_f}
|
|
43
|
-
conn.lpush(history_key, Sidekiq.dump_json(task_trace))
|
|
44
|
-
conn.ltrim(history_key, 0, HISTORY_LIMIT - 1)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def store_enqueue(jid, args)
|
|
49
|
-
time = Time.now.to_f
|
|
50
|
-
store_time(time, "last_enqueue_at")
|
|
51
|
-
store_history(jid, args, time)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def store_execution(jid, time_key)
|
|
55
|
-
update_history_entry(jid) do |entry|
|
|
56
|
-
entry.merge(time_key => Time.now.to_f)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def store_execution_error(jid, error)
|
|
61
|
-
update_history_entry(jid) do |entry|
|
|
62
|
-
error_message = truncate_message("#{error.class}: #{error.message}", ERROR_MESSAGE_MAX_LENGTH)
|
|
63
|
-
entry.merge("error" => error_message)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
private
|
|
68
|
-
|
|
69
|
-
def truncate_message(message, max_length)
|
|
70
|
-
return message if message.length <= max_length
|
|
71
|
-
|
|
72
|
-
"#{message[0...(max_length - 3)]}..."
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def store_time(time, time_key)
|
|
76
|
-
Sidekiq.redis { |conn| conn.hset(jid_key, time_key, time.to_f) }
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def stored_time(time_key)
|
|
80
|
-
timestamp = Sidekiq.redis { |conn| conn.hget(jid_key, time_key) }
|
|
81
|
-
|
|
82
|
-
[nil, ""].include?(timestamp) ? nil : Time.at(timestamp.to_f)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def update_history_entry(jid)
|
|
86
|
-
Sidekiq.redis do |conn|
|
|
87
|
-
entries = conn.lrange(history_key, 0, -1)
|
|
88
|
-
|
|
89
|
-
entries.each_with_index do |raw, index|
|
|
90
|
-
entry = Sidekiq.load_json(raw)
|
|
91
|
-
next unless entry["jid"] == jid
|
|
92
|
-
|
|
93
|
-
updated_entry = yield(entry)
|
|
94
|
-
conn.lset(history_key, index, Sidekiq.dump_json(updated_entry))
|
|
95
|
-
break
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
3
|
+
module Storage
|
|
4
|
+
autoload :Base, "sidekiq/tasks/storage/base"
|
|
5
|
+
autoload :Redis, "sidekiq/tasks/storage/redis"
|
|
99
6
|
end
|
|
100
7
|
end
|
|
101
8
|
end
|
data/lib/sidekiq/tasks/task.rb
CHANGED
|
@@ -22,10 +22,10 @@ module Sidekiq
|
|
|
22
22
|
validate_class!(strategy, [Sidekiq::Tasks::Strategies::Base], "strategy")
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def enqueue(params = {})
|
|
25
|
+
def enqueue(params = {}, user: nil)
|
|
26
26
|
jid = strategy.enqueue_task(name, params)
|
|
27
27
|
|
|
28
|
-
storage.store_enqueue(jid, params)
|
|
28
|
+
storage.store_enqueue(jid, params, user: user)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def execute(params = {}, jid: nil)
|
|
@@ -33,7 +33,7 @@ module Sidekiq
|
|
|
33
33
|
|
|
34
34
|
begin
|
|
35
35
|
strategy.execute_task(name, params)
|
|
36
|
-
rescue => e
|
|
36
|
+
rescue StandardError, SystemExit => e
|
|
37
37
|
storage.store_execution(jid, "finished_at")
|
|
38
38
|
storage.store_execution_error(jid, e)
|
|
39
39
|
raise
|
|
@@ -43,7 +43,7 @@ module Sidekiq
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def storage
|
|
46
|
-
@_storage ||= Sidekiq::Tasks
|
|
46
|
+
@_storage ||= Sidekiq::Tasks.config.storage.new(name, history_limit: Sidekiq::Tasks.config.history_limit)
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
end
|
|
@@ -32,6 +32,14 @@ module Sidekiq
|
|
|
32
32
|
"'#{name}' must be one of #{expected_values.map(&:inspect).join(" or ")} but received #{value.inspect}"
|
|
33
33
|
end
|
|
34
34
|
module_function :validate_expected_values!
|
|
35
|
+
|
|
36
|
+
def validate_subclass!(klass, base_class, name = nil)
|
|
37
|
+
return if klass.is_a?(Class) && klass <= base_class
|
|
38
|
+
|
|
39
|
+
raise Sidekiq::Tasks::ArgumentError,
|
|
40
|
+
"'#{name}' must be a class inheriting from #{base_class.name} but received #{klass.inspect}"
|
|
41
|
+
end
|
|
42
|
+
module_function :validate_subclass!
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
end
|
|
@@ -2,6 +2,7 @@ require "sidekiq/tasks/web/helpers/application_helper"
|
|
|
2
2
|
require "sidekiq/tasks/web/helpers/tag_helper"
|
|
3
3
|
require "sidekiq/tasks/web/helpers/task_helper"
|
|
4
4
|
require "sidekiq/tasks/web/helpers/pagination_helper"
|
|
5
|
+
require "sidekiq/tasks/web/helpers/sort_helper"
|
|
5
6
|
require "sidekiq/tasks/web/search"
|
|
6
7
|
require "sidekiq/tasks/web/pagination"
|
|
7
8
|
require "sidekiq/tasks/web/params"
|
|
@@ -15,11 +16,12 @@ module Sidekiq
|
|
|
15
16
|
app.helpers(Sidekiq::Tasks::Web::Helpers::TagHelper)
|
|
16
17
|
app.helpers(Sidekiq::Tasks::Web::Helpers::TaskHelper)
|
|
17
18
|
app.helpers(Sidekiq::Tasks::Web::Helpers::PaginationHelper)
|
|
19
|
+
app.helpers(Sidekiq::Tasks::Web::Helpers::SortHelper)
|
|
18
20
|
|
|
19
21
|
app.get "/tasks" do
|
|
20
22
|
authorize!
|
|
21
23
|
|
|
22
|
-
@search = Sidekiq::Tasks::Web::Search.new(fetch_params(:count, :page, :filter))
|
|
24
|
+
@search = Sidekiq::Tasks::Web::Search.new(fetch_params(:count, :page, :filter, :sort, :direction))
|
|
23
25
|
|
|
24
26
|
erb(read_view(:tasks), locals: {search: @search})
|
|
25
27
|
end
|
|
@@ -29,7 +31,22 @@ module Sidekiq
|
|
|
29
31
|
|
|
30
32
|
@task = find_task!(env["rack.route_params"][:name])
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
history = @task.history
|
|
35
|
+
per_page = 10
|
|
36
|
+
page = [fetch_param("page").to_i, 1].max
|
|
37
|
+
total_pages = [(history.size.to_f / per_page).ceil, 1].max
|
|
38
|
+
history_entries = history.slice((page - 1) * per_page, per_page) || []
|
|
39
|
+
|
|
40
|
+
erb(
|
|
41
|
+
read_view(:task),
|
|
42
|
+
locals: {
|
|
43
|
+
task: @task,
|
|
44
|
+
history_entries: history_entries,
|
|
45
|
+
history_page: page,
|
|
46
|
+
history_total_pages: total_pages,
|
|
47
|
+
history_total_count: history.size,
|
|
48
|
+
}
|
|
49
|
+
)
|
|
33
50
|
rescue Sidekiq::Tasks::NotFoundError
|
|
34
51
|
throw :halt, [404, {Rack::CONTENT_TYPE => "text/plain"}, ["Task not found"]]
|
|
35
52
|
end
|
|
@@ -44,7 +61,8 @@ module Sidekiq
|
|
|
44
61
|
task = find_task!(env["rack.route_params"][:name])
|
|
45
62
|
args = Sidekiq::Tasks::Web::Params.new(task, fetch_param("args")).permit!
|
|
46
63
|
|
|
47
|
-
|
|
64
|
+
current_user = Sidekiq::Tasks.config.current_user&.call(env)
|
|
65
|
+
task.enqueue(args, user: current_user)
|
|
48
66
|
|
|
49
67
|
redirect(task_url(root_path, task))
|
|
50
68
|
rescue Sidekiq::Tasks::ArgumentError => e
|
|
@@ -6,22 +6,29 @@ module Sidekiq
|
|
|
6
6
|
extend self
|
|
7
7
|
include Sidekiq::Tasks::Web::Helpers::TagHelper
|
|
8
8
|
|
|
9
|
-
def
|
|
9
|
+
def pagination_base_url(search, root_path)
|
|
10
|
+
query_params = {
|
|
11
|
+
filter: search.filter.to_s,
|
|
12
|
+
count: search.count,
|
|
13
|
+
sort: search.sort,
|
|
14
|
+
direction: search.direction,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
"#{root_path}tasks?#{URI.encode_www_form(query_params)}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_pagination_link(link, base_url)
|
|
21
|
+
separator = base_url.include?("?") ? "&" : "?"
|
|
22
|
+
|
|
10
23
|
build_tag(:li, class: "st-page-item") do
|
|
11
24
|
build_tag(
|
|
12
25
|
:a,
|
|
13
26
|
link[:text],
|
|
14
27
|
class: build_classes("st-page-link", disabled: link[:disabled], active: link[:active]),
|
|
15
|
-
href:
|
|
28
|
+
href: "#{base_url}#{separator}page=#{link[:page]}"
|
|
16
29
|
)
|
|
17
30
|
end
|
|
18
31
|
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def pagination_url(root_path, search, page)
|
|
23
|
-
"#{root_path}tasks?filter=#{ERB::Util.url_encode(search.filter)}&count=#{search.count}&page=#{page}"
|
|
24
|
-
end
|
|
25
32
|
end
|
|
26
33
|
end
|
|
27
34
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Sidekiq
|
|
2
|
+
module Tasks
|
|
3
|
+
module Web
|
|
4
|
+
module Helpers
|
|
5
|
+
module SortHelper
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
def sort_header_url(search, root_path, column)
|
|
9
|
+
next_direction = search.toggle_direction(column)
|
|
10
|
+
|
|
11
|
+
query_params = {filter: search.filter.to_s, count: search.count}
|
|
12
|
+
query_params.merge!(sort: column, direction: next_direction) if next_direction
|
|
13
|
+
|
|
14
|
+
"#{root_path}tasks?#{URI.encode_www_form(query_params)}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def sort_header_classes(search, column)
|
|
18
|
+
css = "st-sortable"
|
|
19
|
+
css += " st-sorted-#{search.direction}" if search.sorted_by?(column)
|
|
20
|
+
css
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -3,6 +3,8 @@ module Sidekiq
|
|
|
3
3
|
module Web
|
|
4
4
|
class Search
|
|
5
5
|
DEFAULT_COUNT = 15
|
|
6
|
+
SORT_COLUMNS = ["name", "last_enqueued"].freeze
|
|
7
|
+
SORT_DIRECTIONS = ["asc", "desc"].freeze
|
|
6
8
|
|
|
7
9
|
def self.count_options
|
|
8
10
|
(0..3).map { |index| DEFAULT_COUNT * (2**index) }
|
|
@@ -15,7 +17,7 @@ module Sidekiq
|
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def tasks
|
|
18
|
-
@_tasks ||=
|
|
20
|
+
@_tasks ||= sorted_collection.slice(offset, count) || []
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def filtered_collection
|
|
@@ -40,6 +42,25 @@ module Sidekiq
|
|
|
40
42
|
requested_page.positive? ? requested_page : 1
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
def sort
|
|
46
|
+
SORT_COLUMNS.include?(params[:sort]) ? params[:sort] : SORT_COLUMNS.first
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def direction
|
|
50
|
+
SORT_DIRECTIONS.include?(params[:direction]) ? params[:direction] : SORT_DIRECTIONS.first
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def toggle_direction(column)
|
|
54
|
+
return "asc" unless sort == column
|
|
55
|
+
return "desc" if direction == "asc"
|
|
56
|
+
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def sorted_by?(column)
|
|
61
|
+
sort == column
|
|
62
|
+
end
|
|
63
|
+
|
|
43
64
|
def total_pages
|
|
44
65
|
(filtered_collection.size.to_f / count).ceil
|
|
45
66
|
end
|
|
@@ -47,6 +68,28 @@ module Sidekiq
|
|
|
47
68
|
def offset
|
|
48
69
|
(page - 1) * count
|
|
49
70
|
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def sorted_collection
|
|
75
|
+
sorted = filtered_collection.sort_by { |task| sort_value(task) }
|
|
76
|
+
direction == "desc" ? sorted.reverse : sorted
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def sort_value(task)
|
|
80
|
+
case sort
|
|
81
|
+
when "name"
|
|
82
|
+
task.name.to_s.downcase
|
|
83
|
+
when "last_enqueued"
|
|
84
|
+
sort_value_for_time(task.last_enqueue_at)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def sort_value_for_time(time)
|
|
89
|
+
return time.to_f if time
|
|
90
|
+
|
|
91
|
+
direction == "asc" ? Float::INFINITY : -Float::INFINITY
|
|
92
|
+
end
|
|
50
93
|
end
|
|
51
94
|
end
|
|
52
95
|
end
|
data/lib/sidekiq/tasks/web.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Sidekiq
|
|
|
6
6
|
autoload :Extension, "sidekiq/tasks/web/extension"
|
|
7
7
|
|
|
8
8
|
ROOT = File.expand_path("../../../web", File.dirname(__FILE__))
|
|
9
|
+
SIDEKIQ_GTE_7_0_1 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new("7.0.1")
|
|
9
10
|
SIDEKIQ_GTE_7_3_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new("7.3.0")
|
|
10
11
|
SIDEKIQ_GTE_8_0_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new("8.0.0")
|
|
11
12
|
end
|
data/lib/sidekiq/tasks.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "rake"
|
|
4
4
|
|
|
5
5
|
require_relative "tasks/errors"
|
|
6
|
+
require_relative "tasks/storage"
|
|
6
7
|
require_relative "tasks/strategies"
|
|
7
8
|
require_relative "tasks/validations"
|
|
8
9
|
require_relative "tasks/version"
|
|
@@ -12,7 +13,6 @@ module Sidekiq
|
|
|
12
13
|
autoload :Config, "sidekiq/tasks/config"
|
|
13
14
|
autoload :Job, "sidekiq/tasks/job"
|
|
14
15
|
autoload :Set, "sidekiq/tasks/set"
|
|
15
|
-
autoload :Storage, "sidekiq/tasks/storage"
|
|
16
16
|
autoload :Task, "sidekiq/tasks/task"
|
|
17
17
|
autoload :TaskMetadata, "sidekiq/tasks/task_metadata"
|
|
18
18
|
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
.st-table-container {
|
|
24
24
|
overflow: auto;
|
|
25
|
+
max-width: 100%;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
.st-table {
|
|
@@ -64,9 +65,42 @@
|
|
|
64
65
|
border-radius: 2px;
|
|
65
66
|
padding: 0.2em 0.4em;
|
|
66
67
|
border-radius: 0.2em;
|
|
68
|
+
display: block;
|
|
69
|
+
max-width: 200px;
|
|
70
|
+
overflow: auto;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
.st-table .st-desc-cell {
|
|
70
74
|
white-space: pre-wrap;
|
|
71
75
|
word-wrap: break-word;
|
|
72
76
|
}
|
|
77
|
+
|
|
78
|
+
.st-sortable {
|
|
79
|
+
color: inherit;
|
|
80
|
+
text-decoration: none;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.st-sortable:hover {
|
|
85
|
+
text-decoration: underline;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.st-sorted-asc::after,
|
|
89
|
+
.st-sorted-desc::after {
|
|
90
|
+
content: "";
|
|
91
|
+
display: inline-block;
|
|
92
|
+
margin-left: 5px;
|
|
93
|
+
width: 0;
|
|
94
|
+
height: 0;
|
|
95
|
+
vertical-align: middle;
|
|
96
|
+
border-left: 4px solid transparent;
|
|
97
|
+
border-right: 4px solid transparent;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.st-sorted-asc::after {
|
|
101
|
+
border-bottom: 4px solid currentColor;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.st-sorted-desc::after {
|
|
105
|
+
border-top: 4px solid currentColor;
|
|
106
|
+
}
|
data/web/locales/en.yml
CHANGED
data/web/locales/fr.yml
CHANGED
data/web/views/_pagination.erb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
<% if
|
|
1
|
+
<% if total_pages > 1 %>
|
|
2
2
|
<div class="st-pagination-wrapper">
|
|
3
3
|
<div>
|
|
4
4
|
<small class="st-text-primary">
|
|
5
|
-
<%=
|
|
5
|
+
<%= displayed_count %> / <%= total_count %>
|
|
6
6
|
</small>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
9
|
<ul class="st-pagination">
|
|
10
|
-
<% Sidekiq::Tasks::Web::Pagination.new(
|
|
11
|
-
<%=
|
|
10
|
+
<% Sidekiq::Tasks::Web::Pagination.new(page, total_pages).links.each do |link| %>
|
|
11
|
+
<%= build_pagination_link(link, base_url) %>
|
|
12
12
|
<% end %>
|
|
13
13
|
</ul>
|
|
14
14
|
</div>
|
data/web/views/task.erb
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<% end %>
|
|
7
7
|
<% else %>
|
|
8
8
|
<% add_to_head do %>
|
|
9
|
-
<link href="<%= root_path %>tasks/css/ext.css" media="screen" rel="stylesheet" type="text/css"
|
|
9
|
+
<link href="<%= root_path %>tasks/css/ext.css" media="screen" rel="stylesheet" type="text/css">
|
|
10
10
|
<% end %>
|
|
11
11
|
<% end %>
|
|
12
12
|
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
</header>
|
|
43
43
|
|
|
44
44
|
<div class="st-table-container">
|
|
45
|
-
<% if
|
|
45
|
+
<% if history_entries.empty? %>
|
|
46
46
|
<p><%= t("no_history") %></p>
|
|
47
47
|
<% else %>
|
|
48
48
|
<table class="st-table">
|
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
<tr>
|
|
51
51
|
<th><%= t("jid") %></th>
|
|
52
52
|
<th><%= t("args") %></th>
|
|
53
|
+
<% if Sidekiq::Tasks.config.current_user %>
|
|
54
|
+
<th><%= t("enqueued_by") %></th>
|
|
55
|
+
<% end %>
|
|
53
56
|
<th><%= t("enqueued") %></th>
|
|
54
57
|
<th><%= t("executed") %></th>
|
|
55
58
|
<th><%= t("duration") %></th>
|
|
@@ -57,12 +60,17 @@
|
|
|
57
60
|
</tr>
|
|
58
61
|
</thead>
|
|
59
62
|
<tbody>
|
|
60
|
-
<%
|
|
63
|
+
<% history_entries.each do |jid_history| %>
|
|
61
64
|
<tr>
|
|
62
65
|
<td><%= jid_history["jid"] %></td>
|
|
63
66
|
<td>
|
|
64
67
|
<code><%= jid_history["args"] %></code>
|
|
65
68
|
</td>
|
|
69
|
+
<% if Sidekiq::Tasks.config.current_user %>
|
|
70
|
+
<td>
|
|
71
|
+
<code><%= jid_history["user"] || "-" %></code>
|
|
72
|
+
</td>
|
|
73
|
+
<% end %>
|
|
66
74
|
<td>
|
|
67
75
|
<%= jid_history["enqueued_at"] ? jid_history["enqueued_at"].strftime(t("task_time")) : "-" %>
|
|
68
76
|
</td>
|
|
@@ -77,13 +85,24 @@
|
|
|
77
85
|
:span,
|
|
78
86
|
t(task_status(jid_history).to_s).capitalize,
|
|
79
87
|
class: "st-status-badge #{task_status(jid_history)}",
|
|
80
|
-
"data-tooltip": jid_history["error"]
|
|
88
|
+
"data-tooltip": jid_history["error"]
|
|
81
89
|
) %>
|
|
82
90
|
</td>
|
|
83
91
|
</tr>
|
|
84
92
|
<% end %>
|
|
85
93
|
</tbody>
|
|
86
94
|
</table>
|
|
95
|
+
|
|
96
|
+
<%= erb(
|
|
97
|
+
read_view(:_pagination),
|
|
98
|
+
locals: {
|
|
99
|
+
displayed_count: history_entries.size,
|
|
100
|
+
total_count: history_total_count,
|
|
101
|
+
page: history_page,
|
|
102
|
+
total_pages: history_total_pages,
|
|
103
|
+
base_url: task_url(root_path, task),
|
|
104
|
+
}
|
|
105
|
+
) %>
|
|
87
106
|
<% end %>
|
|
88
107
|
</div>
|
|
89
108
|
|
|
@@ -100,7 +119,7 @@
|
|
|
100
119
|
<% task.args.each do |arg| %>
|
|
101
120
|
<div class="st-form-group">
|
|
102
121
|
<label for="<%= arg %>" class="st-label"><%= arg %></label>
|
|
103
|
-
<input type="text" id="<%= arg %>" class="st-input" name="args[<%= arg %>]"
|
|
122
|
+
<input type="text" id="<%= arg %>" class="st-input" name="args[<%= arg %>]" autocomplete="off">
|
|
104
123
|
</div>
|
|
105
124
|
<% end %>
|
|
106
125
|
</div>
|
|
@@ -109,7 +128,15 @@
|
|
|
109
128
|
<label for="envConfirmationInput" class="st-label">
|
|
110
129
|
<%= t("env_confirmation", current_env: current_env) %>
|
|
111
130
|
</label>
|
|
112
|
-
<input
|
|
131
|
+
<input
|
|
132
|
+
type="text"
|
|
133
|
+
id="envConfirmationInput"
|
|
134
|
+
class="st-input"
|
|
135
|
+
name="env_confirmation"
|
|
136
|
+
data-current-env="<%= current_env %>"
|
|
137
|
+
autocomplete="off"
|
|
138
|
+
required
|
|
139
|
+
>
|
|
113
140
|
</div>
|
|
114
141
|
|
|
115
142
|
<button type="submit" class="st-button" id="submitBtn" disabled>
|
data/web/views/tasks.erb
CHANGED
|
@@ -6,13 +6,23 @@
|
|
|
6
6
|
<% end %>
|
|
7
7
|
<% else %>
|
|
8
8
|
<% add_to_head do %>
|
|
9
|
-
<link href="<%= root_path %>tasks/css/ext.css" media="screen" rel="stylesheet" type="text/css"
|
|
9
|
+
<link href="<%= root_path %>tasks/css/ext.css" media="screen" rel="stylesheet" type="text/css">
|
|
10
10
|
<% end %>
|
|
11
11
|
<% end %>
|
|
12
12
|
|
|
13
13
|
<header class="st-header">
|
|
14
14
|
<form action="<%= root_path %>tasks" method="get" class="st-search-form">
|
|
15
|
-
<input
|
|
15
|
+
<input type="hidden" name="sort" value="<%= @search.sort %>">
|
|
16
|
+
<input type="hidden" name="direction" value="<%= @search.direction %>">
|
|
17
|
+
|
|
18
|
+
<input
|
|
19
|
+
name="filter"
|
|
20
|
+
class="st-input"
|
|
21
|
+
type="text"
|
|
22
|
+
value="<%= Rack::Utils.escape_html(@search.filter.to_s) %>"
|
|
23
|
+
placeholder="<%= t("search") %>"
|
|
24
|
+
autocomplete="off"
|
|
25
|
+
>
|
|
16
26
|
|
|
17
27
|
<select name="count" class="st-select">
|
|
18
28
|
<% Sidekiq::Tasks::Web::Search.count_options.each do |count| %>
|
|
@@ -35,9 +45,23 @@
|
|
|
35
45
|
<table class="st-table">
|
|
36
46
|
<thead>
|
|
37
47
|
<tr>
|
|
38
|
-
<th
|
|
48
|
+
<th>
|
|
49
|
+
<a
|
|
50
|
+
href="<%= sort_header_url(@search, root_path, "name") %>"
|
|
51
|
+
class="<%= sort_header_classes(@search, "name") %>"
|
|
52
|
+
>
|
|
53
|
+
<%= t("name") %>
|
|
54
|
+
</a>
|
|
55
|
+
</th>
|
|
39
56
|
<th><%= t("description") %></th>
|
|
40
|
-
<th
|
|
57
|
+
<th>
|
|
58
|
+
<a
|
|
59
|
+
href="<%= sort_header_url(@search, root_path, "last_enqueued") %>"
|
|
60
|
+
class="<%= sort_header_classes(@search, "last_enqueued") %>"
|
|
61
|
+
>
|
|
62
|
+
<%= t("last_enqueued") %>
|
|
63
|
+
</a>
|
|
64
|
+
</th>
|
|
41
65
|
</tr>
|
|
42
66
|
</thead>
|
|
43
67
|
<tbody>
|
|
@@ -56,6 +80,15 @@
|
|
|
56
80
|
</tbody>
|
|
57
81
|
</table>
|
|
58
82
|
|
|
59
|
-
<%= erb(
|
|
83
|
+
<%= erb(
|
|
84
|
+
read_view(:_pagination),
|
|
85
|
+
locals: {
|
|
86
|
+
displayed_count: @search.tasks.size,
|
|
87
|
+
total_count: Sidekiq::Tasks.tasks.size,
|
|
88
|
+
page: @search.page,
|
|
89
|
+
total_pages: @search.total_pages,
|
|
90
|
+
base_url: pagination_base_url(@search, root_path),
|
|
91
|
+
}
|
|
92
|
+
) %>
|
|
60
93
|
<% end %>
|
|
61
94
|
</div>
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sidekiq-tasks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Victor
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rake
|
|
@@ -151,7 +150,7 @@ dependencies:
|
|
|
151
150
|
- !ruby/object:Gem::Version
|
|
152
151
|
version: '0'
|
|
153
152
|
- !ruby/object:Gem::Dependency
|
|
154
|
-
name:
|
|
153
|
+
name: selenium-webdriver
|
|
155
154
|
requirement: !ruby/object:Gem::Requirement
|
|
156
155
|
requirements:
|
|
157
156
|
- - ">="
|
|
@@ -165,7 +164,7 @@ dependencies:
|
|
|
165
164
|
- !ruby/object:Gem::Version
|
|
166
165
|
version: '0'
|
|
167
166
|
- !ruby/object:Gem::Dependency
|
|
168
|
-
name:
|
|
167
|
+
name: sidekiq
|
|
169
168
|
requirement: !ruby/object:Gem::Requirement
|
|
170
169
|
requirements:
|
|
171
170
|
- - ">="
|
|
@@ -179,7 +178,7 @@ dependencies:
|
|
|
179
178
|
- !ruby/object:Gem::Version
|
|
180
179
|
version: '0'
|
|
181
180
|
- !ruby/object:Gem::Dependency
|
|
182
|
-
name: simplecov
|
|
181
|
+
name: simplecov
|
|
183
182
|
requirement: !ruby/object:Gem::Requirement
|
|
184
183
|
requirements:
|
|
185
184
|
- - ">="
|
|
@@ -193,7 +192,7 @@ dependencies:
|
|
|
193
192
|
- !ruby/object:Gem::Version
|
|
194
193
|
version: '0'
|
|
195
194
|
- !ruby/object:Gem::Dependency
|
|
196
|
-
name:
|
|
195
|
+
name: simplecov-json
|
|
197
196
|
requirement: !ruby/object:Gem::Requirement
|
|
198
197
|
requirements:
|
|
199
198
|
- - ">="
|
|
@@ -237,6 +236,7 @@ files:
|
|
|
237
236
|
- LICENSE.txt
|
|
238
237
|
- README.md
|
|
239
238
|
- Rakefile
|
|
239
|
+
- docs/custom_storage.md
|
|
240
240
|
- docs/task.png
|
|
241
241
|
- lib/sidekiq-tasks.rb
|
|
242
242
|
- lib/sidekiq/tasks.rb
|
|
@@ -245,6 +245,8 @@ files:
|
|
|
245
245
|
- lib/sidekiq/tasks/job.rb
|
|
246
246
|
- lib/sidekiq/tasks/set.rb
|
|
247
247
|
- lib/sidekiq/tasks/storage.rb
|
|
248
|
+
- lib/sidekiq/tasks/storage/base.rb
|
|
249
|
+
- lib/sidekiq/tasks/storage/redis.rb
|
|
248
250
|
- lib/sidekiq/tasks/strategies.rb
|
|
249
251
|
- lib/sidekiq/tasks/strategies/base.rb
|
|
250
252
|
- lib/sidekiq/tasks/strategies/rake_task.rb
|
|
@@ -261,6 +263,7 @@ files:
|
|
|
261
263
|
- lib/sidekiq/tasks/web/extension.rb
|
|
262
264
|
- lib/sidekiq/tasks/web/helpers/application_helper.rb
|
|
263
265
|
- lib/sidekiq/tasks/web/helpers/pagination_helper.rb
|
|
266
|
+
- lib/sidekiq/tasks/web/helpers/sort_helper.rb
|
|
264
267
|
- lib/sidekiq/tasks/web/helpers/tag_helper.rb
|
|
265
268
|
- lib/sidekiq/tasks/web/helpers/task_helper.rb
|
|
266
269
|
- lib/sidekiq/tasks/web/pagination.rb
|
|
@@ -290,7 +293,6 @@ metadata:
|
|
|
290
293
|
homepage_uri: https://github.com/victorauthiat/sidekiq-tasks
|
|
291
294
|
source_code_uri: https://github.com/victorauthiat/sidekiq-tasks/blob/master
|
|
292
295
|
changelog_uri: https://github.com/victorauthiat/sidekiq-tasks/blob/master/CHANGELOG.md
|
|
293
|
-
post_install_message:
|
|
294
296
|
rdoc_options: []
|
|
295
297
|
require_paths:
|
|
296
298
|
- lib
|
|
@@ -305,8 +307,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
305
307
|
- !ruby/object:Gem::Version
|
|
306
308
|
version: '0'
|
|
307
309
|
requirements: []
|
|
308
|
-
rubygems_version: 3.
|
|
309
|
-
signing_key:
|
|
310
|
+
rubygems_version: 3.6.9
|
|
310
311
|
specification_version: 4
|
|
311
312
|
summary: Sidekiq extension for launching tasks.
|
|
312
313
|
test_files: []
|