good_job 3.99.1 → 4.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -61
  3. data/app/charts/good_job/scheduled_by_queue_chart.rb +2 -2
  4. data/app/controllers/good_job/cron_entries_controller.rb +0 -8
  5. data/app/controllers/good_job/metrics_controller.rb +1 -1
  6. data/app/controllers/good_job/performances_controller.rb +5 -9
  7. data/app/models/concerns/good_job/filterable.rb +1 -1
  8. data/app/models/good_job/base_execution.rb +104 -201
  9. data/app/models/good_job/cron_entry.rb +0 -2
  10. data/app/models/good_job/discrete_execution.rb +1 -31
  11. data/app/models/good_job/execution.rb +3 -7
  12. data/app/models/good_job/job.rb +37 -116
  13. data/app/models/good_job/process.rb +7 -20
  14. data/app/views/good_job/batches/index.html.erb +11 -15
  15. data/app/views/good_job/jobs/_executions.erb +1 -1
  16. data/app/views/good_job/jobs/_table.erb +1 -1
  17. data/app/views/good_job/jobs/show.html.erb +1 -8
  18. data/app/views/good_job/performances/show.html.erb +33 -40
  19. data/config/locales/de.yml +1 -4
  20. data/config/locales/en.yml +1 -4
  21. data/config/locales/es.yml +1 -4
  22. data/config/locales/fr.yml +1 -4
  23. data/config/locales/it.yml +1 -4
  24. data/config/locales/ja.yml +1 -4
  25. data/config/locales/ko.yml +1 -4
  26. data/config/locales/nl.yml +1 -4
  27. data/config/locales/pt-BR.yml +1 -4
  28. data/config/locales/ru.yml +1 -4
  29. data/config/locales/tr.yml +1 -4
  30. data/config/locales/uk.yml +1 -4
  31. data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb +65 -3
  32. data/lib/good_job/active_job_extensions/batches.rb +1 -1
  33. data/lib/good_job/active_job_extensions/concurrency.rb +10 -10
  34. data/lib/good_job/adapter.rb +13 -24
  35. data/lib/good_job/current_thread.rb +6 -6
  36. data/lib/good_job/job_performer.rb +2 -2
  37. data/lib/good_job/log_subscriber.rb +2 -10
  38. data/lib/good_job/notifier.rb +3 -3
  39. data/lib/good_job/version.rb +1 -1
  40. data/lib/good_job.rb +17 -22
  41. metadata +16 -30
  42. data/lib/generators/good_job/templates/update/migrations/02_create_good_job_settings.rb.erb +0 -20
  43. data/lib/generators/good_job/templates/update/migrations/03_create_index_good_jobs_jobs_on_priority_created_at_when_unfinished.rb.erb +0 -19
  44. data/lib/generators/good_job/templates/update/migrations/04_create_good_job_batches.rb.erb +0 -35
  45. data/lib/generators/good_job/templates/update/migrations/05_create_good_job_executions.rb.erb +0 -33
  46. data/lib/generators/good_job/templates/update/migrations/06_create_good_jobs_error_event.rb.erb +0 -16
  47. data/lib/generators/good_job/templates/update/migrations/07_recreate_good_job_cron_indexes_with_conditional.rb.erb +0 -45
  48. data/lib/generators/good_job/templates/update/migrations/08_create_good_job_labels.rb.erb +0 -15
  49. data/lib/generators/good_job/templates/update/migrations/09_create_good_job_labels_index.rb.erb +0 -22
  50. data/lib/generators/good_job/templates/update/migrations/10_remove_good_job_active_id_index.rb.erb +0 -21
  51. data/lib/generators/good_job/templates/update/migrations/11_create_index_good_job_jobs_for_candidate_lookup.rb.erb +0 -19
  52. data/lib/generators/good_job/templates/update/migrations/12_create_good_job_execution_error_backtrace.rb.erb +0 -15
  53. data/lib/generators/good_job/templates/update/migrations/13_create_good_job_process_lock_ids.rb.erb +0 -18
  54. data/lib/generators/good_job/templates/update/migrations/14_create_good_job_process_lock_indexes.rb.erb +0 -38
  55. data/lib/generators/good_job/templates/update/migrations/15_create_good_job_execution_duration.rb.erb +0 -15
@@ -14,31 +14,6 @@ module GoodJob # :nodoc:
14
14
 
15
15
  alias_attribute :performed_at, :created_at
16
16
 
17
- def self.error_event_migrated?
18
- return true if columns_hash["error_event"].present?
19
-
20
- migration_pending_warning!
21
- false
22
- end
23
-
24
- def self.backtrace_migrated?
25
- return true if columns_hash["error_backtrace"].present?
26
-
27
- migration_pending_warning!
28
- false
29
- end
30
-
31
- def self.duration_interval_migrated?
32
- return true if columns_hash["duration"].present?
33
-
34
- migration_pending_warning!
35
- false
36
- end
37
-
38
- def self.duration_interval_usable?
39
- duration_interval_migrated? && Gem::Version.new(Rails.version) >= Gem::Version.new('6.1.0.a')
40
- end
41
-
42
17
  def number
43
18
  serialized_params.fetch('executions', 0) + 1
44
19
  end
@@ -50,12 +25,7 @@ module GoodJob # :nodoc:
50
25
 
51
26
  # Monotonic time between when this job started and finished
52
27
  def runtime_latency
53
- # migrated and Rails greater than 6.1
54
- if self.class.duration_interval_usable?
55
- duration
56
- elsif performed_at
57
- (finished_at || Time.current) - performed_at
58
- end
28
+ duration
59
29
  end
60
30
 
61
31
  def last_status_at
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GoodJob
4
- # Active Record model that represents an +ActiveJob+ job.
5
- # Most behavior is currently in BaseExecution in anticipation of
6
- # moving behavior into +GoodJob::Job+.
7
- class Execution < BaseExecution
8
- self.table_name = 'good_jobs'
9
-
10
- belongs_to :job, class_name: 'GoodJob::Job', foreign_key: 'active_job_id', primary_key: 'active_job_id', optional: true, inverse_of: :executions
4
+ # Created at the time a Job begins executing.
5
+ # Behavior from +DiscreteExecution+ will be merged into this class.
6
+ class Execution < DiscreteExecution
11
7
  end
12
8
  end
@@ -2,10 +2,6 @@
2
2
 
3
3
  module GoodJob
4
4
  # Active Record model that represents an +ActiveJob+ job.
5
- # There is not a table in the database whose discrete rows represents "Jobs".
6
- # The +good_jobs+ table is a table of individual {GoodJob::Execution}s that share the same +active_job_id+.
7
- # A single row from the +good_jobs+ table of executions is fetched to represent a Job.
8
- #
9
5
  class Job < BaseExecution
10
6
  # Raised when an inappropriate action is applied to a Job based on its state.
11
7
  ActionForStateMismatchError = Class.new(StandardError)
@@ -16,29 +12,17 @@ module GoodJob
16
12
  # Raised when Active Job data cannot be deserialized
17
13
  ActiveJobDeserializationError = Class.new(StandardError)
18
14
 
19
- class << self
20
- delegate :table_name, to: GoodJob::Execution
21
-
22
- def table_name=(_value)
23
- raise NotImplementedError, 'Assign GoodJob::Execution.table_name directly'
24
- end
25
- end
26
-
27
- self.primary_key = 'active_job_id'
28
- self.advisory_lockable_column = 'active_job_id'
15
+ self.table_name = 'good_jobs'
16
+ self.advisory_lockable_column = 'id'
29
17
  self.implicit_order_column = 'created_at'
30
18
 
31
19
  belongs_to :batch, class_name: 'GoodJob::BatchRecord', inverse_of: :jobs, optional: true
32
20
  belongs_to :locked_by_process, class_name: "GoodJob::Process", foreign_key: :locked_by_id, inverse_of: :locked_jobs, optional: true
33
- has_many :executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', inverse_of: :job # rubocop:disable Rails/HasManyOrHasOneDependent
34
- has_many :discrete_executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::DiscreteExecution', foreign_key: 'active_job_id', primary_key: :active_job_id, inverse_of: :job # rubocop:disable Rails/HasManyOrHasOneDependent
21
+ has_many :executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', primary_key: :active_job_id, inverse_of: :job, dependent: :delete_all
22
+ # TODO: rename callers of discrete_execution to executions, but after v4 has some time to bake for cleaner diffs/patches
23
+ has_many :discrete_executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', primary_key: :active_job_id, inverse_of: :job, dependent: :delete_all
35
24
 
36
- after_destroy lambda {
37
- GoodJob::DiscreteExecution.where(active_job_id: active_job_id).delete_all if discrete? # TODO: move into association `dependent: :delete_all` after v4
38
- }
39
-
40
- # Only the most-recent unretried execution represents a "Job"
41
- default_scope { where(retried_good_job_id: nil) }
25
+ before_create -> { self.id = active_job_id }, if: -> { active_job_id.present? }
42
26
 
43
27
  # Get Jobs finished before the given timestamp.
44
28
  # @!method finished_before(timestamp)
@@ -64,65 +48,11 @@ module GoodJob
64
48
 
65
49
  scope :unfinished_undiscrete, -> { where(finished_at: nil, retried_good_job_id: nil, is_discrete: [nil, false]) }
66
50
 
67
- # The job's Active Job UUID
68
- # @return [String]
69
- def id
70
- active_job_id
71
- end
72
-
73
- # Override #reload to add a custom scope to ensure the reloaded record is the head execution
74
- # @return [Job]
75
- def reload(options = nil)
76
- self.class.connection.clear_query_cache
77
-
78
- # override with the `where(retried_good_job_id: nil)` scope
79
- override_query = self.class.where(retried_good_job_id: nil)
80
- fresh_object =
81
- if options && options[:lock]
82
- self.class.unscoped { override_query.lock(options[:lock]).find(id) }
83
- else
84
- self.class.unscoped { override_query.find(id) }
85
- end
86
-
87
- @attributes = fresh_object.instance_variable_get(:@attributes)
88
- @new_record = false
89
- @previously_new_record = false
90
- self
91
- end
92
-
93
- # This job's most recent {Execution}
94
- # @param reload [Booelan] whether to reload executions
95
- # @return [Execution]
96
- def head_execution(reload: false)
97
- executions.reload if reload
98
- executions.load # memoize the results
99
- executions.last
100
- end
101
-
102
- # This job's initial/oldest {Execution}
103
- # @return [Execution]
104
- def tail_execution
105
- executions.first
106
- end
107
-
108
- # The number of times this job has been executed, according to Active Job's serialized state.
109
- # @return [Numeric]
110
- def executions_count
111
- aj_count = serialized_params.fetch('executions', 0)
112
- # The execution count within serialized_params is not updated
113
- # once the underlying execution has been executed.
114
- if status.in? [:discarded, :succeeded, :running]
115
- aj_count + 1
116
- else
117
- aj_count
118
- end
119
- end
120
-
121
- # The number of times this job has been executed, according to the number of GoodJob {Execution} records.
122
- # @return [Numeric]
123
- def preserved_executions_count
124
- executions.size
125
- end
51
+ # TODO: it would be nice to enforce these values at the model
52
+ # validates :active_job_id, presence: true
53
+ # validates :scheduled_at, presence: true
54
+ # validates :job_class, presence: true
55
+ # validates :error_event, presence: true, if: -> { error.present? }
126
56
 
127
57
  # The most recent error message.
128
58
  # If the job has been retried, the error will be fetched from the previous {Execution} record.
@@ -155,6 +85,10 @@ module GoodJob
155
85
  job_class
156
86
  end
157
87
 
88
+ def executions_count
89
+ super || 0
90
+ end
91
+
158
92
  # Tests whether the job is being executed right now.
159
93
  # @return [Boolean]
160
94
  def running?
@@ -189,33 +123,33 @@ module GoodJob
189
123
  # @return [ActiveJob::Base]
190
124
  def retry_job
191
125
  with_advisory_lock do
192
- execution = head_execution(reload: true)
193
- active_job = execution.active_job(ignore_deserialization_errors: true)
126
+ reload
127
+ active_job = self.active_job(ignore_deserialization_errors: true)
194
128
 
195
129
  raise ActiveJobDeserializationError if active_job.nil?
196
130
  raise AdapterNotGoodJobError unless active_job.class.queue_adapter.is_a? GoodJob::Adapter
197
- raise ActionForStateMismatchError if execution.finished_at.blank? || execution.error.blank?
131
+ raise ActionForStateMismatchError if finished_at.blank? || error.blank?
198
132
 
199
133
  # Update the executions count because the previous execution will not have been preserved
200
134
  # Do not update `exception_executions` because that comes from rescue_from's arguments
201
135
  active_job.executions = (active_job.executions || 0) + 1
202
136
 
203
137
  begin
204
- error_class, error_message = execution.error.split(ERROR_MESSAGE_SEPARATOR).map(&:strip)
138
+ error_class, error_message = error.split(ERROR_MESSAGE_SEPARATOR).map(&:strip)
205
139
  error = error_class.constantize.new(error_message)
206
140
  rescue StandardError
207
- error = StandardError.new(execution.error)
141
+ error = StandardError.new(error)
208
142
  end
209
143
 
210
144
  new_active_job = nil
211
145
  GoodJob::CurrentThread.within do |current_thread|
212
- current_thread.execution = execution
146
+ current_thread.job = self
213
147
  current_thread.retry_now = true
214
148
 
215
- execution.class.transaction(joinable: false, requires_new: true) do
149
+ self.class.transaction(joinable: false, requires_new: true) do
216
150
  new_active_job = active_job.retry_job(wait: 0, error: error)
217
- execution.error_event = ERROR_EVENT_RETRIED if execution.error && execution.class.error_event_migrated?
218
- execution.save!
151
+ self.error_event = ERROR_EVENT_RETRIED if error
152
+ save!
219
153
  end
220
154
  end
221
155
 
@@ -244,11 +178,10 @@ module GoodJob
244
178
  # @return [void]
245
179
  def reschedule_job(scheduled_at = Time.current)
246
180
  with_advisory_lock do
247
- execution = head_execution(reload: true)
248
-
249
- raise ActionForStateMismatchError if execution.finished_at.present?
181
+ reload
182
+ raise ActionForStateMismatchError if finished_at.present?
250
183
 
251
- execution.update(scheduled_at: scheduled_at)
184
+ update(scheduled_at: scheduled_at)
252
185
  end
253
186
  end
254
187
 
@@ -256,9 +189,7 @@ module GoodJob
256
189
  # @return [void]
257
190
  def destroy_job
258
191
  with_advisory_lock do
259
- execution = head_execution(reload: true)
260
-
261
- raise ActionForStateMismatchError if execution.finished_at.blank?
192
+ raise ActionForStateMismatchError if finished_at.blank?
262
193
 
263
194
  destroy
264
195
  end
@@ -270,37 +201,27 @@ module GoodJob
270
201
  attributes['id']
271
202
  end
272
203
 
273
- # Utility method to test whether this job's underlying attributes represents its most recent execution.
274
- # @return [Boolean]
275
- def _head?
276
- _execution_id == head_execution(reload: true).id
277
- end
278
-
279
204
  private
280
205
 
281
206
  def _discard_job(message)
282
- execution = head_execution(reload: true)
283
- active_job = execution.active_job(ignore_deserialization_errors: true)
207
+ active_job = self.active_job(ignore_deserialization_errors: true)
284
208
 
285
- raise ActionForStateMismatchError if execution.finished_at.present?
209
+ raise ActionForStateMismatchError if finished_at.present?
286
210
 
287
211
  job_error = GoodJob::Job::DiscardJobError.new(message)
288
212
 
289
- update_execution = proc do
290
- execution.update(
291
- {
292
- finished_at: Time.current,
293
- error: GoodJob::Execution.format_error(job_error),
294
- }.tap do |attrs|
295
- attrs[:error_event] = ERROR_EVENT_DISCARDED if self.class.error_event_migrated?
296
- end
213
+ update_record = proc do
214
+ update(
215
+ finished_at: Time.current,
216
+ error: self.class.format_error(job_error),
217
+ error_event: ERROR_EVENT_DISCARDED
297
218
  )
298
219
  end
299
220
 
300
221
  if active_job.respond_to?(:instrument)
301
- active_job.send :instrument, :discard, error: job_error, &update_execution
222
+ active_job.send :instrument, :discard, error: job_error, &update_record
302
223
  else
303
- update_execution.call
224
+ update_record.call
304
225
  end
305
226
  end
306
227
  end
@@ -33,13 +33,9 @@ module GoodJob # :nodoc:
33
33
  # @!scope class
34
34
  # @return [ActiveRecord::Relation]
35
35
  scope :active, (lambda do
36
- if lock_type_migrated?
37
- query = joins_advisory_locks
38
- query.where(lock_type: LOCK_TYPE_ENUMS[LOCK_TYPE_ADVISORY]).advisory_locked
39
- .or(query.where(lock_type: nil).where(arel_table[:updated_at].gt(EXPIRED_INTERVAL.ago)))
40
- else
41
- advisory_locked
42
- end
36
+ query = joins_advisory_locks
37
+ query.where(lock_type: LOCK_TYPE_ENUMS[LOCK_TYPE_ADVISORY]).advisory_locked
38
+ .or(query.where(lock_type: nil).where(arel_table[:updated_at].gt(EXPIRED_INTERVAL.ago)))
43
39
  end)
44
40
 
45
41
  # Processes that are inactive and unlocked (e.g. SIGKILLed)
@@ -47,13 +43,9 @@ module GoodJob # :nodoc:
47
43
  # @!scope class
48
44
  # @return [ActiveRecord::Relation]
49
45
  scope :inactive, (lambda do
50
- if lock_type_migrated?
51
- query = joins_advisory_locks
52
- query.where(lock_type: LOCK_TYPE_ENUMS[LOCK_TYPE_ADVISORY]).advisory_unlocked
53
- .or(query.where(lock_type: nil).where(arel_table[:updated_at].lt(EXPIRED_INTERVAL.ago)))
54
- else
55
- advisory_unlocked
56
- end
46
+ query = joins_advisory_locks
47
+ query.where(lock_type: LOCK_TYPE_ENUMS[LOCK_TYPE_ADVISORY]).advisory_unlocked
48
+ .or(query.where(lock_type: nil).where(arel_table[:updated_at].lt(EXPIRED_INTERVAL.ago)))
57
49
  end)
58
50
 
59
51
  # Deletes all inactive process records.
@@ -64,11 +56,6 @@ module GoodJob # :nodoc:
64
56
  end
65
57
  end
66
58
 
67
- # @return [Boolean]
68
- def self.lock_type_migrated?
69
- columns_hash["lock_type"].present?
70
- end
71
-
72
59
  def self.create_record(id:, with_advisory_lock: false)
73
60
  attributes = {
74
61
  id: id,
@@ -76,7 +63,7 @@ module GoodJob # :nodoc:
76
63
  }
77
64
  if with_advisory_lock
78
65
  attributes[:create_with_advisory_lock] = true
79
- attributes[:lock_type] = LOCK_TYPE_ADVISORY if lock_type_migrated?
66
+ attributes[:lock_type] = LOCK_TYPE_ADVISORY
80
67
  end
81
68
  create!(attributes)
82
69
  end
@@ -2,19 +2,15 @@
2
2
  <h2 class="pt-3 pb-2"><%= t ".title" %></h2>
3
3
  </div>
4
4
 
5
- <% if GoodJob::BatchRecord.migrated? %>
6
- <%= render 'good_job/batches/table', batches: @filter.records, filter: @filter %>
7
- <% if @filter.records.present? %>
8
- <nav aria-label="Batch pagination" class="mt-3">
9
- <ul class="pagination">
10
- <li class="page-item">
11
- <%= link_to(@filter.to_params(after_created_at: @filter.last.created_at, after_id: @filter.last.id), class: "page-link") do %>
12
- <%= t ".older_batches" %> <span aria-hidden="true">&raquo;</span>
13
- <% end %>
14
- </li>
15
- </ul>
16
- </nav>
17
- <% end %>
18
- <% else %>
19
- <h3 class="text-center my-5"><%= t ".pending_migrations" %></h3>
5
+ <%= render 'good_job/batches/table', batches: @filter.records, filter: @filter %>
6
+ <% if @filter.records.present? %>
7
+ <nav aria-label="Batch pagination" class="mt-3">
8
+ <ul class="pagination">
9
+ <li class="page-item">
10
+ <%= link_to(@filter.to_params(after_created_at: @filter.last.created_at, after_id: @filter.last.id), class: "page-link") do %>
11
+ <%= t ".older_batches" %> <span aria-hidden="true">&raquo;</span>
12
+ <% end %>
13
+ </li>
14
+ </ul>
15
+ </nav>
20
16
  <% end %>
@@ -38,7 +38,7 @@
38
38
  <strong class="small text-danger"><%= t "good_job.shared.error" %>:</strong>
39
39
  <code class="text-wrap text-break m-0 text-secondary-emphasis"><%= execution.error %></code>
40
40
  </div>
41
- <% if GoodJob::DiscreteExecution.backtrace_migrated? && execution.error_backtrace&.any? %>
41
+ <% if execution.error_backtrace&.any? %>
42
42
  <%= tag.ul class: "nav nav-tabs small w-fit-content", id: dom_id(execution, :tab), role: "tablist" do %>
43
43
  <li class="nav-item" role="presentation">
44
44
  <%= tag.button t(".application_trace"), class: "nav-link active p-1", id: dom_id(execution, :application), data: { bs_toggle: "tab", bs_target: dom_id(execution, :"#application_pane") }, type: "button", role: "tab", aria: { controls: dom_id(execution, :application_pane), selected: true } %>
@@ -102,7 +102,7 @@
102
102
  <%= tag.span relative_time(job.last_status_at), class: "small mt-1" %>
103
103
  <div>
104
104
  <%= status_badge job.status %>
105
- <% if job.status == :discarded && job.class.error_event_migrated? && job.error_event %>
105
+ <% if job.status == :discarded && job.error_event %>
106
106
  <div class="text-black text-center">
107
107
  <small><%= t(job.error_event, scope: 'good_job.error_event') %></small>
108
108
  </div>
@@ -5,9 +5,6 @@
5
5
  <li class="breadcrumb-item"><%= link_to t(".jobs"), jobs_path %></li>
6
6
  <li class="breadcrumb-item active" aria-current="page">
7
7
  <%= tag.code @job.id, class: "text-muted" %>
8
- <% if @job.discrete? %>
9
- <span class="badge bg-info">Discrete</span>
10
- <% end %>
11
8
  </li>
12
9
  </ol>
13
10
  </nav>
@@ -87,8 +84,4 @@
87
84
  <%= tag.pre JSON.pretty_generate(@job.display_serialized_params) %>
88
85
  <% end %>
89
86
 
90
- <% if @job.discrete? %>
91
- <%= render 'executions', executions: @job.discrete_executions.reverse %>
92
- <% else %>
93
- <%= render 'executions', executions: @job.executions.includes_advisory_locks.reverse %>
94
- <% end %>
87
+ <%= render 'executions', executions: @job.executions.reverse %>
@@ -2,49 +2,42 @@
2
2
  <h2 class="pt-3 pb-2"><%= t ".title" %></h2>
3
3
  </div>
4
4
 
5
- <% if @needs_upgrade %>
6
- <div class="alert alert-warning">
7
- <%= t "shared.needs_migration" %>
8
- </div>
9
- <% else %>
10
-
11
- <div class="my-3 card">
12
- <div class="list-group list-group-flush text-nowrap" role="table">
13
- <header class="list-group-item bg-body-tertiary">
14
- <div class="row small text-muted text-uppercase align-items-center">
15
- <div class="col-12 col-lg-4"><%= t ".job_class" %></div>
16
- <div class="col-lg-2 d-none d-lg-block"><%= t ".executions" %></div>
5
+ <div class="my-3 card">
6
+ <div class="list-group list-group-flush text-nowrap" role="table">
7
+ <header class="list-group-item bg-body-tertiary">
8
+ <div class="row small text-muted text-uppercase align-items-center">
9
+ <div class="col-12 col-lg-4"><%= t ".job_class" %></div>
10
+ <div class="col-lg-2 d-none d-lg-block"><%= t ".executions" %></div>
17
11
 
18
- <div class="col-lg-2 d-none d-lg-block"><%= t ".average_duration" %></div>
19
- <div class="col-lg-2 d-none d-lg-block"><%= t ".minimum_duration" %></div>
20
- <div class="col-lg-2 d-none d-lg-block"><%= t ".maximum_duration" %></div>
21
- </div>
22
- </header>
12
+ <div class="col-lg-2 d-none d-lg-block"><%= t ".average_duration" %></div>
13
+ <div class="col-lg-2 d-none d-lg-block"><%= t ".minimum_duration" %></div>
14
+ <div class="col-lg-2 d-none d-lg-block"><%= t ".maximum_duration" %></div>
15
+ </div>
16
+ </header>
23
17
 
24
- <% @performances.each do |performance| %>
25
- <div role="row" class="list-group-item py-3">
26
- <div class="row align-items-center">
27
- <div class="col-12 col-lg-4"><%= performance.job_class %></div>
28
- <div class="col-6 col-lg-2 text-wrap">
29
- <div class="d-lg-none small text-muted mt-1"><%= t ".executions" %></div>
30
- <%= performance.executions_count %>
31
- </div>
18
+ <% @performances.each do |performance| %>
19
+ <div role="row" class="list-group-item py-3">
20
+ <div class="row align-items-center">
21
+ <div class="col-12 col-lg-4"><%= performance.job_class %></div>
22
+ <div class="col-6 col-lg-2 text-wrap">
23
+ <div class="d-lg-none small text-muted mt-1"><%= t ".executions" %></div>
24
+ <%= performance.executions_count %>
25
+ </div>
32
26
 
33
- <div class="col-6 col-lg-2 text-wrap">
34
- <div class="d-lg-none small text-muted mt-1"><%= t ".average_duration" %></div>
35
- <%= format_duration performance.avg_duration %>
36
- </div>
37
- <div class="col-6 col-lg-2 text-wrap">
38
- <div class="d-lg-none small text-muted mt-1"><%= t ".minimum_duration" %></div>
39
- <%= format_duration performance.min_duration %>
40
- </div>
41
- <div class="col-6 col-lg-2 text-wrap">
42
- <div class="d-lg-none small text-muted mt-1"><%= t ".maximum_duration" %></div>
43
- <%= format_duration performance.max_duration %>
44
- </div>
27
+ <div class="col-6 col-lg-2 text-wrap">
28
+ <div class="d-lg-none small text-muted mt-1"><%= t ".average_duration" %></div>
29
+ <%= format_duration performance.avg_duration %>
30
+ </div>
31
+ <div class="col-6 col-lg-2 text-wrap">
32
+ <div class="d-lg-none small text-muted mt-1"><%= t ".minimum_duration" %></div>
33
+ <%= format_duration performance.min_duration %>
34
+ </div>
35
+ <div class="col-6 col-lg-2 text-wrap">
36
+ <div class="d-lg-none small text-muted mt-1"><%= t ".maximum_duration" %></div>
37
+ <%= format_duration performance.max_duration %>
45
38
  </div>
46
39
  </div>
47
- <% end %>
48
- </div>
40
+ </div>
41
+ <% end %>
49
42
  </div>
50
- <% end %>
43
+ </div>
@@ -11,7 +11,6 @@ de:
11
11
  batches:
12
12
  index:
13
13
  older_batches: Ältere Chargen
14
- pending_migrations: GoodJob hat ausstehende Datenbankmigrationen.
15
14
  title: Chargen
16
15
  jobs:
17
16
  actions:
@@ -48,7 +47,6 @@ de:
48
47
  index:
49
48
  no_cron_schedules_found: Keine Cron-Zeitpläne gefunden.
50
49
  title: Cron-Zeitpläne
51
- pending_migrations: Erfordert ausstehende GoodJob-Datenbankmigration.
52
50
  show:
53
51
  cron_entry_key: Cron-Eingabetaste
54
52
  datetime:
@@ -240,6 +238,7 @@ de:
240
238
  dark: Dunkel
241
239
  light: Licht
242
240
  theme: Thema
241
+ pending_migrations: GoodJob hat ausstehende Datenbankmigrationen.
243
242
  secondary_navbar:
244
243
  inspiration: Denk daran, auch du machst einen guten Job!
245
244
  last_updated: Zuletzt aktualisiert
@@ -250,5 +249,3 @@ de:
250
249
  running: Laufend
251
250
  scheduled: Geplant
252
251
  succeeded: Erfolgreich
253
- shared:
254
- needs_migration: Bitte führen Sie GoodJob-Migrationen aus.
@@ -11,7 +11,6 @@ en:
11
11
  batches:
12
12
  index:
13
13
  older_batches: Older batches
14
- pending_migrations: GoodJob has pending database migrations.
15
14
  title: Batches
16
15
  jobs:
17
16
  actions:
@@ -48,7 +47,6 @@ en:
48
47
  index:
49
48
  no_cron_schedules_found: No cron schedules found.
50
49
  title: Cron Schedules
51
- pending_migrations: Requires pending GoodJob database migration.
52
50
  show:
53
51
  cron_entry_key: Cron Entry Key
54
52
  datetime:
@@ -240,6 +238,7 @@ en:
240
238
  dark: Dark
241
239
  light: Light
242
240
  theme: Theme
241
+ pending_migrations: GoodJob has pending database migrations.
243
242
  secondary_navbar:
244
243
  inspiration: Remember, you're doing a Good Job too!
245
244
  last_updated: Last updated
@@ -250,5 +249,3 @@ en:
250
249
  running: Running
251
250
  scheduled: Scheduled
252
251
  succeeded: Succeeded
253
- shared:
254
- needs_migration: Please run GoodJob migrations.
@@ -11,7 +11,6 @@ es:
11
11
  batches:
12
12
  index:
13
13
  older_batches: Batches anteriores
14
- pending_migrations: GoodJob tiene migraciones pendientes.
15
14
  title: Batches
16
15
  jobs:
17
16
  actions:
@@ -48,7 +47,6 @@ es:
48
47
  index:
49
48
  no_cron_schedules_found: No hay tareas programadas.
50
49
  title: Tareas Programadas
51
- pending_migrations: Require las migraciones de GoodJob pendientes.
52
50
  show:
53
51
  cron_entry_key: Cron Entry Key
54
52
  datetime:
@@ -240,6 +238,7 @@ es:
240
238
  dark: Oscuro
241
239
  light: Luz
242
240
  theme: Tema
241
+ pending_migrations: GoodJob tiene migraciones pendientes.
243
242
  secondary_navbar:
244
243
  inspiration: "¡Recuerda, también tú estás haciendo un buen trabajo!"
245
244
  last_updated: Última actualización
@@ -250,5 +249,3 @@ es:
250
249
  running: Ejecutando
251
250
  scheduled: Programado
252
251
  succeeded: Exitoso
253
- shared:
254
- needs_migration: Ejecute las migraciones de GoodJob.
@@ -11,7 +11,6 @@ fr:
11
11
  batches:
12
12
  index:
13
13
  older_batches: Lots plus anciens
14
- pending_migrations: GoodJob a des migrations de bases de données en attente.
15
14
  title: Lots
16
15
  jobs:
17
16
  actions:
@@ -48,7 +47,6 @@ fr:
48
47
  index:
49
48
  no_cron_schedules_found: Aucune planification cron trouvée.
50
49
  title: Entrées Cron
51
- pending_migrations: Nécessite une migration de la base de données GoodJob en attente.
52
50
  show:
53
51
  cron_entry_key: Clé d'entrée Cron
54
52
  datetime:
@@ -240,6 +238,7 @@ fr:
240
238
  dark: Sombre
241
239
  light: Lumière
242
240
  theme: Thème
241
+ pending_migrations: GoodJob a des migrations de bases de données en attente.
243
242
  secondary_navbar:
244
243
  inspiration: N'oublie pas, toi aussi tu fais du bon boulot !
245
244
  last_updated: Dernière mise à jour
@@ -250,5 +249,3 @@ fr:
250
249
  running: En cours
251
250
  scheduled: Planifiés
252
251
  succeeded: Réussis
253
- shared:
254
- needs_migration: Veuillez exécuter des migrations GoodJob.