sidekiq-unique-jobs 6.0.25 → 7.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +155 -20
  3. data/README.md +349 -112
  4. data/lib/sidekiq-unique-jobs.rb +2 -0
  5. data/lib/sidekiq_unique_jobs.rb +43 -6
  6. data/lib/sidekiq_unique_jobs/batch_delete.rb +121 -0
  7. data/lib/sidekiq_unique_jobs/changelog.rb +71 -0
  8. data/lib/sidekiq_unique_jobs/cli.rb +20 -29
  9. data/lib/sidekiq_unique_jobs/config.rb +193 -0
  10. data/lib/sidekiq_unique_jobs/connection.rb +5 -4
  11. data/lib/sidekiq_unique_jobs/constants.rb +36 -24
  12. data/lib/sidekiq_unique_jobs/core_ext.rb +38 -0
  13. data/lib/sidekiq_unique_jobs/digests.rb +78 -93
  14. data/lib/sidekiq_unique_jobs/exceptions.rb +152 -8
  15. data/lib/sidekiq_unique_jobs/job.rb +3 -3
  16. data/lib/sidekiq_unique_jobs/json.rb +34 -0
  17. data/lib/sidekiq_unique_jobs/key.rb +93 -0
  18. data/lib/sidekiq_unique_jobs/lock.rb +295 -0
  19. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +49 -43
  20. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  21. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  22. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +8 -17
  23. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +5 -5
  24. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +1 -23
  25. data/lib/sidekiq_unique_jobs/lock/validator.rb +65 -0
  26. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +12 -8
  27. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +1 -1
  28. data/lib/sidekiq_unique_jobs/lock_config.rb +95 -0
  29. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  30. data/lib/sidekiq_unique_jobs/locksmith.rb +255 -99
  31. data/lib/sidekiq_unique_jobs/logging.rb +148 -22
  32. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  33. data/lib/sidekiq_unique_jobs/lua/delete.lua +51 -0
  34. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +46 -0
  35. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  36. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  37. data/lib/sidekiq_unique_jobs/lua/find_digest_in_sorted_set.lua +24 -0
  38. data/lib/sidekiq_unique_jobs/lua/lock.lua +91 -0
  39. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  40. data/lib/sidekiq_unique_jobs/lua/queue.lua +83 -0
  41. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +86 -0
  42. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +40 -0
  43. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  44. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +19 -0
  45. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +18 -0
  46. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +46 -0
  47. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  48. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  49. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  50. data/lib/sidekiq_unique_jobs/lua/shared/find_digest_in_sorted_set.lua +24 -0
  51. data/lib/sidekiq_unique_jobs/lua/unlock.lua +99 -0
  52. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  53. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +68 -0
  54. data/lib/sidekiq_unique_jobs/middleware.rb +62 -31
  55. data/lib/sidekiq_unique_jobs/middleware/client.rb +42 -0
  56. data/lib/sidekiq_unique_jobs/middleware/server.rb +27 -0
  57. data/lib/sidekiq_unique_jobs/normalizer.rb +3 -3
  58. data/lib/sidekiq_unique_jobs/on_conflict.rb +22 -9
  59. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +8 -4
  60. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +59 -13
  61. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +42 -13
  62. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +4 -4
  63. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +24 -5
  64. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +47 -23
  65. data/lib/sidekiq_unique_jobs/orphans/manager.rb +100 -0
  66. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  67. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +201 -0
  68. data/lib/sidekiq_unique_jobs/profiler.rb +51 -0
  69. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  70. data/lib/sidekiq_unique_jobs/redis/entity.rb +94 -0
  71. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  72. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  73. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  74. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +59 -0
  75. data/lib/sidekiq_unique_jobs/redis/string.rb +49 -0
  76. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +19 -0
  77. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +43 -0
  78. data/lib/sidekiq_unique_jobs/{scripts.rb → script.rb} +43 -29
  79. data/lib/sidekiq_unique_jobs/script/caller.rb +125 -0
  80. data/lib/sidekiq_unique_jobs/script/template.rb +41 -0
  81. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +92 -65
  82. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +166 -28
  83. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +10 -11
  84. data/lib/sidekiq_unique_jobs/testing.rb +47 -15
  85. data/lib/sidekiq_unique_jobs/time_calculator.rb +103 -0
  86. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  87. data/lib/sidekiq_unique_jobs/unique_args.rb +19 -21
  88. data/lib/sidekiq_unique_jobs/unlockable.rb +11 -2
  89. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  90. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +151 -0
  91. data/lib/sidekiq_unique_jobs/version.rb +3 -1
  92. data/lib/sidekiq_unique_jobs/version_check.rb +1 -1
  93. data/lib/sidekiq_unique_jobs/web.rb +25 -19
  94. data/lib/sidekiq_unique_jobs/web/helpers.rb +98 -6
  95. data/lib/sidekiq_unique_jobs/web/views/lock.erb +108 -0
  96. data/lib/sidekiq_unique_jobs/web/views/locks.erb +52 -0
  97. data/lib/tasks/changelog.rake +4 -3
  98. metadata +70 -35
  99. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -56
  100. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -46
  101. data/lib/sidekiq_unique_jobs/timeout.rb +0 -8
  102. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +0 -63
  103. data/lib/sidekiq_unique_jobs/util.rb +0 -103
  104. data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +0 -28
  105. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +0 -46
  106. data/redis/acquire_lock.lua +0 -21
  107. data/redis/convert_legacy_lock.lua +0 -13
  108. data/redis/delete.lua +0 -14
  109. data/redis/delete_by_digest.lua +0 -23
  110. data/redis/delete_job_by_digest.lua +0 -60
  111. data/redis/lock.lua +0 -62
  112. data/redis/release_stale_locks.lua +0 -90
  113. data/redis/unlock.lua +0 -35
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Class UpdateVersion sets the right version in redis
6
+ #
7
+ # @author Mikael Henriksson <mikael@zoolutions.se>
8
+ #
9
+ class UpdateVersion
10
+ #
11
+ # Sets the right versions in redis
12
+ #
13
+ # @note the version isn't used yet but will be for automatic upgrades
14
+ #
15
+ # @return [true] when version changed
16
+ #
17
+ def self.call
18
+ Script::Caller.call_script(
19
+ :update_version,
20
+ keys: [LIVE_VERSION, DEAD_VERSION],
21
+ argv: [SidekiqUniqueJobs.version],
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Upgrades locks between gem version upgrades
6
+ #
7
+ # @author Mikael Henriksson <mikael@zoolutions.se>
8
+ #
9
+ class UpgradeLocks # rubocop:disable Metrics/ClassLength
10
+ BATCH_SIZE = 100
11
+ OLD_SUFFIXES = %w[
12
+ GRABBED
13
+ AVAILABLE
14
+ EXISTS
15
+ VERSION
16
+ ].freeze
17
+
18
+ include SidekiqUniqueJobs::Logging
19
+ include SidekiqUniqueJobs::Connection
20
+
21
+ #
22
+ # Performs upgrade of old locks
23
+ #
24
+ #
25
+ # @return [Integer] the number of upgrades locks
26
+ #
27
+ def self.call
28
+ redis do |conn|
29
+ new(conn).call
30
+ end
31
+ end
32
+
33
+ attr_reader :conn
34
+
35
+ def initialize(conn)
36
+ @count = 0
37
+ @conn = conn
38
+ redis_version # Avoid pipelined calling redis_version and getting a future.
39
+ end
40
+
41
+ #
42
+ # Performs upgrade of old locks
43
+ #
44
+ #
45
+ # @return [Integer] the number of upgrades locks
46
+ #
47
+ def call
48
+ with_logging_context do
49
+ return log_info("Already upgraded to #{version}") if conn.hget(upgraded_key, version)
50
+ # TODO: Needs handling of v7.0.0 => v7.0.1 where we don't want to
51
+ return log_info("Skipping upgrade because #{DEAD_VERSION} has been set") if conn.get(DEAD_VERSION)
52
+
53
+ log_info("Start - Upgrading Locks")
54
+
55
+ upgrade_v6_locks
56
+ delete_unused_v6_keys
57
+ delete_supporting_v6_keys
58
+
59
+ conn.hset(upgraded_key, version, now_f)
60
+ log_info("Done - Upgrading Locks")
61
+ end
62
+
63
+ @count
64
+ end
65
+
66
+ private
67
+
68
+ def upgraded_key
69
+ @upgraded_key ||= "#{LIVE_VERSION}:UPGRADED"
70
+ end
71
+
72
+ def upgrade_v6_locks
73
+ log_info("Start - Converting v6 locks to v7")
74
+ conn.scan_each(match: "*:GRABBED", count: BATCH_SIZE) do |grabbed_key|
75
+ upgrade_v6_lock(grabbed_key)
76
+ @count += 1
77
+ end
78
+ log_info("Done - Converting v6 locks to v7")
79
+ end
80
+
81
+ def upgrade_v6_lock(grabbed_key)
82
+ locked_key = grabbed_key.gsub(":GRABBED", ":LOCKED")
83
+ digest = grabbed_key.gsub(":GRABBED", "")
84
+ locks = conn.hgetall(grabbed_key)
85
+
86
+ conn.pipelined do
87
+ conn.hmset(locked_key, *locks.to_a)
88
+ conn.zadd(DIGESTS, locks.values.first, digest)
89
+ end
90
+ end
91
+
92
+ def delete_unused_v6_keys
93
+ log_info("Start - Deleting v6 keys")
94
+ OLD_SUFFIXES.each do |suffix|
95
+ delete_suffix(suffix)
96
+ end
97
+ log_info("Done - Deleting v6 keys")
98
+ end
99
+
100
+ def delete_supporting_v6_keys
101
+ batch_delete("unique:keys")
102
+ end
103
+
104
+ def delete_suffix(suffix)
105
+ batch_scan(match: "*:#{suffix}", count: BATCH_SIZE) do |keys|
106
+ batch_delete(*keys)
107
+ end
108
+ end
109
+
110
+ def batch_delete(*keys)
111
+ return if keys.empty?
112
+
113
+ conn.pipelined do
114
+ if VersionCheck.satisfied?(redis_version, ">= 4.0.0")
115
+ conn.unlink(*keys)
116
+ else
117
+ conn.del(*keys)
118
+ end
119
+ end
120
+ end
121
+
122
+ def batch_scan(match:, count:)
123
+ cursor = "0"
124
+ loop do
125
+ cursor, values = conn.scan(cursor, match: match, per: count)
126
+ yield values
127
+ break if cursor == "0"
128
+ end
129
+ end
130
+
131
+ def version
132
+ SidekiqUniqueJobs.version
133
+ end
134
+
135
+ def now_f
136
+ SidekiqUniqueJobs.now_f
137
+ end
138
+
139
+ def redis_version
140
+ @redis_version ||= SidekiqUniqueJobs.config.redis_version
141
+ end
142
+
143
+ def logging_context
144
+ if logger_context_hash?
145
+ { "uniquejobs" => :upgrade_locks }
146
+ else
147
+ "uniquejobs-upgrade_locks"
148
+ end
149
+ end
150
+ end
151
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SidekiqUniqueJobs
4
- VERSION = "6.0.25"
4
+ #
5
+ # @return [String] the current SidekiqUniqueJobs version
6
+ VERSION = "7.0.0.beta2"
5
7
  end
@@ -7,7 +7,7 @@ module SidekiqUniqueJobs
7
7
  # @author Mikael Henriksson <mikael@zoolutions.se>
8
8
  #
9
9
  class VersionCheck
10
- PATTERN = /(?<operator1>[<>=]+)?\s?(?<version1>(\d+.?)+)(\s+&&\s+)?(?<operator2>[<>=]+)?\s?(?<version2>(\d+.?)+)?/m.freeze # rubocop:disable Layout/LineLength
10
+ PATTERN = /(?<operator1>[<>=]+)?\s?(?<version1>(\d+.?)+)(\s+&&\s+)?(?<operator2>[<>=]+)?\s?(?<version2>(\d+.?)+)?/m.freeze # rubocop:disable Metrics/LineLength
11
11
 
12
12
  #
13
13
  # Checks if a version is consrtaint is satisfied
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require "delegate"
5
- require "rack"
6
4
  require "sidekiq/web"
7
- rescue LoadError
5
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
8
6
  # client-only usage
9
7
  end
10
8
 
@@ -16,38 +14,46 @@ module SidekiqUniqueJobs
16
14
  #
17
15
  # @author Mikael Henriksson <mikael@zoolutions.se>
18
16
  module Web
19
- def self.registered(app) # rubocop:disable Metrics/MethodLength
17
+ def self.registered(app) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
20
18
  app.helpers do
21
19
  include Web::Helpers
22
20
  end
23
21
 
24
- app.get "/unique_digests" do
22
+ app.get "/locks" do
25
23
  @filter = params[:filter] || "*"
26
24
  @filter = "*" if @filter == ""
27
25
  @count = (params[:count] || 100).to_i
28
26
  @current_cursor = params[:cursor]
29
27
  @prev_cursor = params[:prev_cursor]
30
- @total_size, @next_cursor, @unique_digests =
31
- SidekiqUniqueJobs::Digests.page(pattern: @filter, cursor: @current_cursor, page_size: @count)
28
+ @pagination = { pattern: @filter, cursor: @current_cursor, page_size: @count }
29
+ @total_size, @next_cursor, @locks = digests.page(@pagination)
32
30
 
33
- erb(unique_template(:unique_digests))
31
+ erb(unique_template(:locks))
34
32
  end
35
33
 
36
- app.get "/unique_digests/delete_all" do
37
- SidekiqUniqueJobs::Digests.delete_by_pattern("*", count: SidekiqUniqueJobs::Digests.count)
38
- redirect_to :unique_digests
34
+ app.get "/locks/delete_all" do
35
+ digests.del(pattern: "*", count: digests.count)
36
+ redirect_to :locks
39
37
  end
40
38
 
41
- app.get "/unique_digests/:digest" do
39
+ app.get "/locks/:digest" do
42
40
  @digest = params[:digest]
43
- @unique_keys = SidekiqUniqueJobs::Util.keys("#{@digest}*", 1000)
41
+ @lock = SidekiqUniqueJobs::Lock.new(@digest)
44
42
 
45
- erb(unique_template(:unique_digest))
43
+ erb(unique_template(:lock))
46
44
  end
47
45
 
48
- app.get "/unique_digests/:digest/delete" do
49
- SidekiqUniqueJobs::Digests.delete_by_digest(params[:digest])
50
- redirect_to :unique_digests
46
+ app.get "/locks/:digest/delete" do
47
+ digests.del(digest: params[:digest])
48
+ redirect_to :locks
49
+ end
50
+
51
+ app.get "/locks/:digest/jobs/:job_id/delete" do
52
+ @digest = params[:digest]
53
+ @lock = SidekiqUniqueJobs::Lock.new(@digest)
54
+ @lock.unlock(params[:job_id])
55
+
56
+ redirect_to "locks/#{@lock.key}"
51
57
  end
52
58
  end
53
59
  end
@@ -55,6 +61,6 @@ end
55
61
 
56
62
  if defined?(Sidekiq::Web)
57
63
  Sidekiq::Web.register SidekiqUniqueJobs::Web
58
- Sidekiq::Web.tabs["Unique Digests"] = "unique_digests"
59
- # Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), 'locales')
64
+ Sidekiq::Web.tabs["Locks"] = "locks"
65
+ Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "locales")
60
66
  end
@@ -2,15 +2,45 @@
2
2
 
3
3
  module SidekiqUniqueJobs
4
4
  module Web
5
+ #
6
+ # Provides view helpers for the Sidekiq::Web extension
7
+ #
8
+ # @author Mikael Henriksson <mikael@zoolutions.se>
9
+ #
5
10
  module Helpers
6
- VIEW_PATH = File.expand_path("../web/views", __dir__)
11
+ VIEW_PATH = File.expand_path("../web/views", __dir__)
12
+ SAFE_CPARAMS = %w[cursor prev_cursor].freeze
13
+
14
+ module_function
7
15
 
16
+ #
17
+ # Opens a template file contained within this gem
18
+ #
19
+ # @param [Symbol] name the name of the template
20
+ #
21
+ # @return [String] the file contents of the template
22
+ #
8
23
  def unique_template(name)
9
24
  File.open(File.join(VIEW_PATH, "#{name}.erb")).read
10
25
  end
11
26
 
12
- SAFE_CPARAMS = %w[cursor prev_cursor].freeze
27
+ #
28
+ # The collection of digests
29
+ #
30
+ #
31
+ # @return [SidekiqUniqueJobs::Digests] the sorted set with digests
32
+ #
33
+ def digests
34
+ @digests ||= SidekiqUniqueJobs::Digests.new
35
+ end
13
36
 
37
+ #
38
+ # Creates url safe parameters
39
+ #
40
+ # @param [Hash] options the key/value to parameterize
41
+ #
42
+ # @return [String] a url safe parameter string
43
+ #
14
44
  def cparams(options)
15
45
  # stringify
16
46
  options.keys.each do |key|
@@ -24,6 +54,35 @@ module SidekiqUniqueJobs
24
54
  end.compact.join("&")
25
55
  end
26
56
 
57
+ #
58
+ # Used to avoid incompatibility with older sidekiq versions
59
+ #
60
+ #
61
+ # @param [Array] args the unique arguments to display
62
+ # @param [Integer] truncate_after_chars
63
+ #
64
+ # @return [String] a string containing all non-truncated arguments
65
+ #
66
+ def display_unique_args(args, truncate_after_chars = 2000)
67
+ return "Invalid job payload, args is nil" if args.nil?
68
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
69
+
70
+ begin
71
+ args.map do |arg|
72
+ h(truncate(to_display(arg), truncate_after_chars))
73
+ end.join(", ")
74
+ rescue StandardError
75
+ "Illegal job arguments: #{h args.inspect}"
76
+ end
77
+ end
78
+
79
+ #
80
+ # Redirect to with falback
81
+ #
82
+ # @param [String] subpath the path to redirect to
83
+ #
84
+ # @return a redirect to the new subpath
85
+ #
27
86
  def redirect_to(subpath)
28
87
  if respond_to?(:to)
29
88
  # Sinatra-based web UI
@@ -34,14 +93,47 @@ module SidekiqUniqueJobs
34
93
  end
35
94
  end
36
95
 
96
+ #
97
+ # Gets a relative time as html
98
+ #
99
+ # @param [Time] time an instance of Time
100
+ #
101
+ # @return [String] a html safe string with relative time information
102
+ #
103
+ def relative_time(time)
104
+ stamp = time.getutc.iso8601
105
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
106
+ end
107
+
108
+ #
109
+ # Gets a relative time as html without crashing
110
+ #
111
+ # @param [Float, Integer, String, Time] time a representation of a timestamp
112
+ #
113
+ # @return [String] a html safe string with relative time information
114
+ #
37
115
  def safe_relative_time(time)
38
- time = if time.is_a?(Numeric)
116
+ time = parse_time(time)
117
+
118
+ relative_time(time)
119
+ end
120
+
121
+ #
122
+ # Constructs a time from a number of different types
123
+ #
124
+ # @param [Float, Integer, String, Time] time a representation of a timestamp
125
+ #
126
+ # @return [Time]
127
+ #
128
+ def parse_time(time)
129
+ case time
130
+ when Time
131
+ time
132
+ when Integer, Float
39
133
  Time.at(time)
40
134
  else
41
- Time.parse(time)
135
+ Time.parse(time.to_s)
42
136
  end
43
-
44
- relative_time(time)
45
137
  end
46
138
  end
47
139
  end
@@ -0,0 +1,108 @@
1
+ <header class="row">
2
+ <div class="col-sm-5">
3
+ <h3>
4
+ <%= t('Lock information') %> <a class="btn btn-default btn-xs" href="<%= root_path %>locks"><%= t('GoBack') %></a>
5
+ </h3>
6
+ </div>
7
+ <div class="col-sm-7 table-responsive">
8
+ <% if @lock.info.none? %>
9
+ <h3>No Lock Information Available</h3>
10
+ <p>To use it turn the following setting on:
11
+ <code>SidekiqUniqueJobs.config.lock_info = true</code>
12
+ </p>
13
+ <% else %>
14
+ <table class="table table-striped table-bordered table-white table-hover">
15
+ <caption>Information about lock</caption>
16
+ <tbody>
17
+ <tr>
18
+ <th scope=row><%= t('Worker') %></td>
19
+ <td><%= @lock.info["worker"] %></td>
20
+ </tr>
21
+ <tr>
22
+ <th scope=row><%= t('Queue') %></td>
23
+ <td><%= @lock.info["queue"] %></td>
24
+ </tr>
25
+ <tr>
26
+ <th scope=row><%= t('Limit') %></td>
27
+ <td><%= @lock.info["limit"] %></td>
28
+ </tr>
29
+ <tr>
30
+ <th scope=row><%= t('TTL') %></td>
31
+ <td><%= @lock.info["ttl"] %></td>
32
+ </tr>
33
+ <tr>
34
+ <th scope=row><%= t('Timeout') %></td>
35
+ <td><%= @lock.info["timeout"] %></td>
36
+ </tr>
37
+ <tr>
38
+ <th scope=row><%= t('Unique Args') %></td>
39
+ <td>
40
+ <code class="code-wrap">
41
+ <!-- We don't want to truncate any job arguments when viewing a single job's status page -->
42
+ <div class="args-extended"><%= display_unique_args(@lock.info["unique_args"], nil) %></div>
43
+ </code>
44
+ </td>
45
+ </tr>
46
+ </tbody>
47
+ </table>
48
+ <% end %>
49
+ </div>
50
+ </header>
51
+ <div class="row">
52
+ <div class="col-sm-6 table-responsive">
53
+ <table class="table table-striped table-bordered table-hover">
54
+ <caption>Digest: <strong class="text-muted"><%= @lock.key %></strong></caption>
55
+ <thead>
56
+ <tr>
57
+ <th scope="col"><%= t('Locked JIDs') %></th>
58
+ <th scope="col"><%= t('Since') %></th>
59
+ <th scope="col"></th>
60
+ </tr>
61
+ </thead>
62
+ <tbody>
63
+ <% @lock.locked_jids(with_values: true).each do |job_id, time| %>
64
+ <tr>
65
+ <td><%= job_id %></td>
66
+ <td><%= safe_relative_time(time.to_f) %></td>
67
+ <td>
68
+ <form action="<%= root_path %>locks/<%= @lock.key %>/jobs/<%= job_id %>/delete" method="get">
69
+ <%= csrf_tag %>
70
+ <input class="btn btn-danger btn-xs flip" type="submit" name="delete" value="<%= t('Unlock') %>" data-confirm="<%= t('AreYouSure') %>" />
71
+ </form>
72
+ </td>
73
+ </tr>
74
+ <% end %>
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ <div class="col-sm-6 table-responsive">
79
+ <% if @lock.changelog.count.positive? %>
80
+ <table class="table table-striped table-bordered table-hover">
81
+ <caption>Changelogs</caption>
82
+ <thead>
83
+ <tr>
84
+ <th scope="col"><%= t('At') %></th>
85
+ <th scope="col"><%= t('JID') %></th>
86
+ <th scope="col"><%= t('Message') %></th>
87
+ <th scope="col"><%= t('Script') %></th>
88
+ </tr>
89
+ </thead>
90
+ <tbody>
91
+ <% @lock.changelogs.each do |entry| %>
92
+ <tr>
93
+ <td scope="row"><%= safe_relative_time(entry["time"].to_f) %></td>
94
+ <td><%= entry["job_id"] %></td>
95
+ <td><%= entry["message"] %></td>
96
+ <td><%= entry["script"] %></td>
97
+ </tr>
98
+ <% end %>
99
+ </tbody>
100
+ </table>
101
+ <% end %>
102
+ </div>
103
+ </div>
104
+ <form action="<%= root_path %>locks/<%= @lock.key %>/delete" method="get">
105
+ <%= csrf_tag %>
106
+ <a class="btn btn-default btn-xs" href="<%= root_path %>locks"><%= t('GoBack') %></a>
107
+ <input class="btn btn-danger btn-xs flip" type="submit" name="delete" value="<%= t('Delete') %>" data-confirm="<%= t('AreYouSure') %>" />
108
+ </form>