good_job 3.18.3 → 3.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 930bb77e947ac155ee655210ab15ef32ac40fc2d6487eaf80b0005d7e5675156
4
- data.tar.gz: b3e26f0b7f4d60483a6d0646d5797654e447542b8d6cb9eca22e2cb7cbfaac78
3
+ metadata.gz: 6c66d226179c2cd55bfc539c943df468fc683f761b89cdc10606f03a6bb26f87
4
+ data.tar.gz: f72404539fb5bf7ef122cf65ed616276dd0457538b1319e253d6078e223ddbbb
5
5
  SHA512:
6
- metadata.gz: d92deaa0fa930cd8e8bf9b6b51f37c6d8ec1b75d221d939b6b5d2cb2ec30153931873951bbfca9af5dd24867e6131133f865368845a2cf783e8212137ed3bd76
7
- data.tar.gz: 0c3ff2fd89cb38061ab145d74180596334fc8f38cba5f2a61d11fcf791d03534b823361bd9546cea4cea316d04d3cfba979d459b1b3d8185386298cdb71593a0
6
+ metadata.gz: 98d03d3abdbcc20956e37e8c498c8fa57c1b2b091c5f50c0654aa75875d9d23ca02aeb78d67f5f2a8c961a9aabfc0ad3899fcf1867c2b4350dfe56584568a4eb
7
+ data.tar.gz: f0044b4c761371391c5aec83500efece2e000a6f9741a504f244208ae403b42fd39e1324fe1e753e3ae4d67c6ae92706f9a960ab5ec2745df547acd4e0927ac7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.19.0](https://github.com/bensheldon/good_job/tree/v3.19.0) (2023-09-19)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.18.3...v3.19.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - "Force" discard jobs that are already running/runaway to prevent retry [\#1073](https://github.com/bensheldon/good_job/pull/1073) ([jgrau](https://github.com/jgrau))
10
+
11
+ **Closed issues:**
12
+
13
+ - Possible Memory Leak [\#1074](https://github.com/bensheldon/good_job/issues/1074)
14
+ - What's the best way to stop and discard a running job? [\#625](https://github.com/bensheldon/good_job/issues/625)
15
+
16
+ **Merged pull requests:**
17
+
18
+ - AdvisoryLockable: Abort record create if with\_advisory\_lock fails to acquire advisory lock [\#1078](https://github.com/bensheldon/good_job/pull/1078) ([bensheldon](https://github.com/bensheldon))
19
+ - Wrap all test background threads in Rails executors; better test logging/debugging [\#1077](https://github.com/bensheldon/good_job/pull/1077) ([bensheldon](https://github.com/bensheldon))
20
+
3
21
  ## [v3.18.3](https://github.com/bensheldon/good_job/tree/v3.18.3) (2023-09-16)
4
22
 
5
23
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.18.2...v3.18.3)
@@ -3,6 +3,7 @@
3
3
  module GoodJob
4
4
  class JobsController < GoodJob::ApplicationController
5
5
  DISCARD_MESSAGE = "Discarded through dashboard"
6
+ FORCE_DISCARD_MESSAGE = "Force discarded through dashboard"
6
7
 
7
8
  ACTIONS = {
8
9
  discard: "discarded",
@@ -66,6 +67,12 @@ module GoodJob
66
67
  redirect_back(fallback_location: jobs_path, notice: t(".notice"))
67
68
  end
68
69
 
70
+ def force_discard
71
+ @job = Job.find(params[:id])
72
+ @job.force_discard_job(FORCE_DISCARD_MESSAGE)
73
+ redirect_back(fallback_location: jobs_path, notice: t(".notice"))
74
+ end
75
+
69
76
  def reschedule
70
77
  @job = Job.find(params[:id])
71
78
  @job.reschedule_job
@@ -133,7 +133,12 @@ module GoodJob
133
133
  # @return [Boolean]
134
134
  attr_accessor :create_with_advisory_lock
135
135
 
136
- after_create -> { advisory_lock }, if: :create_with_advisory_lock
136
+ after_create lambda {
137
+ advisory_lock || begin
138
+ errors.add(self.class.advisory_lockable_column, "Failed to acquire advisory lock: #{lockable_key}")
139
+ raise ActiveRecord::RecordInvalid # do not reference the record because it can cause I18n missing translation error
140
+ end
141
+ }, if: :create_with_advisory_lock
137
142
  end
138
143
 
139
144
  class_methods do
@@ -222,6 +227,47 @@ module GoodJob
222
227
  connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Unlock', binds).first['unlocked']
223
228
  end
224
229
 
230
+ # Tests whether the provided key has an advisory lock on it.
231
+ # @param key [String, Symbol] Key to test lock against
232
+ # @return [Boolean]
233
+ def advisory_locked_key?(key)
234
+ query = <<~SQL.squish
235
+ SELECT 1 AS one
236
+ FROM pg_locks
237
+ WHERE pg_locks.locktype = 'advisory'
238
+ AND pg_locks.objsubid = 1
239
+ AND pg_locks.classid = ('x' || substr(md5($1::text), 1, 16))::bit(32)::int
240
+ AND pg_locks.objid = (('x' || substr(md5($2::text), 1, 16))::bit(64) << 32)::bit(32)::int
241
+ LIMIT 1
242
+ SQL
243
+ binds = [
244
+ ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
245
+ ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
246
+ ]
247
+ connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Locked?', binds).any?
248
+ end
249
+
250
+ # Tests whether this record is locked by the current database session.
251
+ # @param key [String, Symbol] Key to test lock against
252
+ # @return [Boolean]
253
+ def owns_advisory_lock_key?(key)
254
+ query = <<~SQL.squish
255
+ SELECT 1 AS one
256
+ FROM pg_locks
257
+ WHERE pg_locks.locktype = 'advisory'
258
+ AND pg_locks.objsubid = 1
259
+ AND pg_locks.classid = ('x' || substr(md5($1::text), 1, 16))::bit(32)::int
260
+ AND pg_locks.objid = (('x' || substr(md5($2::text), 1, 16))::bit(64) << 32)::bit(32)::int
261
+ AND pg_locks.pid = pg_backend_pid()
262
+ LIMIT 1
263
+ SQL
264
+ binds = [
265
+ ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
266
+ ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
267
+ ]
268
+ connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Owns Advisory Lock?', binds).any?
269
+ end
270
+
225
271
  def _advisory_lockable_column
226
272
  advisory_lockable_column || primary_key
227
273
  end
@@ -318,20 +364,7 @@ module GoodJob
318
364
  # @param key [String, Symbol] Key to test lock against
319
365
  # @return [Boolean]
320
366
  def advisory_locked?(key: lockable_key)
321
- query = <<~SQL.squish
322
- SELECT 1 AS one
323
- FROM pg_locks
324
- WHERE pg_locks.locktype = 'advisory'
325
- AND pg_locks.objsubid = 1
326
- AND pg_locks.classid = ('x' || substr(md5($1::text), 1, 16))::bit(32)::int
327
- AND pg_locks.objid = (('x' || substr(md5($2::text), 1, 16))::bit(64) << 32)::bit(32)::int
328
- LIMIT 1
329
- SQL
330
- binds = [
331
- ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
332
- ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new),
333
- ]
334
- self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Locked?', binds).any?
367
+ self.class.advisory_locked_key?(key)
335
368
  end
336
369
 
337
370
  # Tests whether this record does not have an advisory lock on it.
@@ -345,6 +378,7 @@ module GoodJob
345
378
  # @param key [String, Symbol] Key to test lock against
346
379
  # @return [Boolean]
347
380
  def owns_advisory_lock?(key: lockable_key)
381
+ self.class.owns_advisory_lock_key?(key)
348
382
  query = <<~SQL.squish
349
383
  SELECT 1 AS one
350
384
  FROM pg_locks
@@ -30,10 +30,10 @@ module GoodJob
30
30
 
31
31
  # Runs the block with self.logger silenced.
32
32
  # If self.logger is nil, simply runs the block.
33
- def self.with_logger_silenced(&block)
33
+ def self.with_logger_silenced(silent: true, &block)
34
34
  # Assign to a local variable, just in case it's modified in another thread concurrently
35
35
  logger = self.logger
36
- if logger.respond_to? :silence
36
+ if silent && logger.respond_to?(:silence)
37
37
  logger.silence(&block)
38
38
  else
39
39
  yield
@@ -215,32 +215,17 @@ module GoodJob
215
215
  # @return [void]
216
216
  def discard_job(message)
217
217
  with_advisory_lock do
218
- execution = head_execution(reload: true)
219
- active_job = execution.active_job(ignore_deserialization_errors: true)
220
-
221
- raise ActionForStateMismatchError if execution.finished_at.present?
222
-
223
- job_error = GoodJob::Job::DiscardJobError.new(message)
224
-
225
- update_execution = proc do
226
- execution.update(
227
- {
228
- finished_at: Time.current,
229
- error: GoodJob::Execution.format_error(job_error),
230
- }.tap do |attrs|
231
- attrs[:error_event] = ERROR_EVENT_DISCARDED if self.class.error_event_migrated?
232
- end
233
- )
234
- end
235
-
236
- if active_job.respond_to?(:instrument)
237
- active_job.send :instrument, :discard, error: job_error, &update_execution
238
- else
239
- update_execution.call
240
- end
218
+ _discard_job(message)
241
219
  end
242
220
  end
243
221
 
222
+ # Force discard a job so that it will not be executed further. Force discard allows discarding
223
+ # a running job.
224
+ # This action will add a {DiscardJobError} to the job's {Execution} and mark it as finished.
225
+ def force_discard_job(message)
226
+ _discard_job(message)
227
+ end
228
+
244
229
  # Reschedule a scheduled job so that it executes immediately (or later) by the next available execution thread.
245
230
  # @param scheduled_at [DateTime, Time] When to reschedule the job
246
231
  # @return [void]
@@ -277,5 +262,33 @@ module GoodJob
277
262
  def _head?
278
263
  _execution_id == head_execution(reload: true).id
279
264
  end
265
+
266
+ private
267
+
268
+ def _discard_job(message)
269
+ execution = head_execution(reload: true)
270
+ active_job = execution.active_job(ignore_deserialization_errors: true)
271
+
272
+ raise ActionForStateMismatchError if execution.finished_at.present?
273
+
274
+ job_error = GoodJob::Job::DiscardJobError.new(message)
275
+
276
+ update_execution = proc do
277
+ execution.update(
278
+ {
279
+ finished_at: Time.current,
280
+ error: GoodJob::Execution.format_error(job_error),
281
+ }.tap do |attrs|
282
+ attrs[:error_event] = ERROR_EVENT_DISCARDED if self.class.error_event_migrated?
283
+ end
284
+ )
285
+ end
286
+
287
+ if active_job.respond_to?(:instrument)
288
+ active_job.send :instrument, :discard, error: job_error, &update_execution
289
+ else
290
+ update_execution.call
291
+ end
292
+ end
280
293
  end
281
294
  end
@@ -127,6 +127,13 @@
127
127
  <%=t "good_job.actions.discard" %>
128
128
  <% end %>
129
129
  </li>
130
+ <li>
131
+ <% job_force_discardable = job.status.in? [:running] %>
132
+ <%= link_to force_discard_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job_force_discardable}", title: t("good_job.jobs.actions.force_discard"), data: { confirm: t("good_job.jobs.actions.confirm_force_discard"), disable: true } do %>
133
+ <%= render "good_job/shared/icons/eject" %>
134
+ <%=t "good_job.actions.force_discard" %>
135
+ <% end %>
136
+ </li>
130
137
  <li>
131
138
  <%= link_to retry_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job.status == :discarded}", title: t("good_job.jobs.actions.retry"), data: { confirm: t("good_job.jobs.actions.confirm_retry"), disable: true } do %>
132
139
  <%= render "good_job/shared/icons/arrow_clockwise" %>
@@ -0,0 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/eject/ -->
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eject" viewBox="0 0 16 16">
3
+ <path d="M7.27 1.047a1 1 0 0 1 1.46 0l6.345 6.77c.6.638.146 1.683-.73 1.683H1.656C.78 9.5.326 8.455.926 7.816L7.27 1.047zM14.346 8.5 8 1.731 1.654 8.5h12.692zM.5 11.5a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1v-1zm14 0h-13v1h13v-1z" />
4
+ </svg>
@@ -4,6 +4,7 @@ de:
4
4
  actions:
5
5
  destroy: Zerstören
6
6
  discard: Verwerfen
7
+ force_discard: Verwerfen erzwingen
7
8
  inspect: Prüfen
8
9
  reschedule: Umplanen
9
10
  retry: Wiederholen
@@ -110,10 +111,14 @@ de:
110
111
  actions:
111
112
  confirm_destroy: Sind Sie sicher, dass Sie den Job zerstören wollen?
112
113
  confirm_discard: Sind Sie sicher, dass Sie den Job verwerfen wollen?
114
+ confirm_force_discard: 'Sind Sie sicher, dass Sie das Verwerfen dieses Jobs erzwingen möchten? Der Job wird als verworfen markiert, aber der laufende Job wird nicht gestoppt – er wird jedoch bei Fehlern nicht erneut versucht.
115
+
116
+ '
113
117
  confirm_reschedule: Möchten Sie den Auftrag wirklich verschieben?
114
118
  confirm_retry: Möchten Sie den Job wirklich wiederholen?
115
119
  destroy: Arbeit vernichten
116
120
  discard: Auftrag verwerfen
121
+ force_discard: Job verwerfen erzwingen
117
122
  reschedule: Auftrag neu planen
118
123
  retry: Job wiederholen
119
124
  destroy:
@@ -124,6 +129,8 @@ de:
124
129
  in_queue: in der Warteschlange
125
130
  runtime: Laufzeit
126
131
  title: Hinrichtungen
132
+ force_discard:
133
+ notice: Der Job wurde zwangsweise verworfen. Die Ausführung wird fortgesetzt, bei Fehlern wird der Vorgang jedoch nicht wiederholt
127
134
  index:
128
135
  job_pagination: Job-Paginierung
129
136
  older_jobs: Ältere Berufe
@@ -4,6 +4,7 @@ en:
4
4
  actions:
5
5
  destroy: Destroy
6
6
  discard: Discard
7
+ force_discard: Force discard
7
8
  inspect: Inspect
8
9
  reschedule: Reschedule
9
10
  retry: Retry
@@ -110,10 +111,14 @@ en:
110
111
  actions:
111
112
  confirm_destroy: Are you sure you want to destroy the job?
112
113
  confirm_discard: Are you usure you want to discard the job?
114
+ confirm_force_discard: 'Are you sure you want to force discard this job? The job will be marked as discarded but the running job will not be stopped - it will, however, not be retried on failures.
115
+
116
+ '
113
117
  confirm_reschedule: Are you sure you want to reschedule the job?
114
118
  confirm_retry: Are you sure you want to retry the job?
115
119
  destroy: Destroy job
116
120
  discard: Discard job
121
+ force_discard: Force discard job
117
122
  reschedule: Reschedule job
118
123
  retry: Retry job
119
124
  destroy:
@@ -124,6 +129,8 @@ en:
124
129
  in_queue: in queue
125
130
  runtime: runtime
126
131
  title: Executions
132
+ force_discard:
133
+ notice: Job has been force discarded. It will continue to run but it will not be retried on failures
127
134
  index:
128
135
  job_pagination: Job pagination
129
136
  older_jobs: Older jobs
@@ -4,6 +4,7 @@ es:
4
4
  actions:
5
5
  destroy: Eliminar
6
6
  discard: Descartar
7
+ force_discard: Forzar descarte
7
8
  inspect: Inspeccionar
8
9
  reschedule: Reprogramar
9
10
  retry: Reintentar
@@ -110,10 +111,12 @@ es:
110
111
  actions:
111
112
  confirm_destroy: "¿Estás seguro que querés eliminar esta tarea?"
112
113
  confirm_discard: "¿Estás seguro que querés descartar esta tarea?"
114
+ confirm_force_discard: "¿Está seguro de que desea forzar el descarte de este trabajo? El trabajo se marcará como descartado, pero el trabajo en ejecución no se detendrá; sin embargo, no se volverá a intentar en caso de falla.\n"
113
115
  confirm_reschedule: "¿Estás seguro que querés reprogramar esta tarea?"
114
116
  confirm_retry: "¿Estás seguro que querés reintentar esta tarea?"
115
117
  destroy: Eliminar tarea
116
118
  discard: Descartar tarea
119
+ force_discard: Forzar el descarte del trabajo
117
120
  reschedule: Reprogramar tarea
118
121
  retry: Reiuntentar tarea
119
122
  destroy:
@@ -124,6 +127,8 @@ es:
124
127
  in_queue: en cola
125
128
  runtime: en ejecución
126
129
  title: Ejecuciones
130
+ force_discard:
131
+ notice: Job ha sido descartado a la fuerza. Continuará ejecutándose pero no se volverá a intentar en caso de fallas.
127
132
  index:
128
133
  job_pagination: Paginación de tareas
129
134
  older_jobs: Tareas anteriores
@@ -4,6 +4,7 @@ fr:
4
4
  actions:
5
5
  destroy: Détruire
6
6
  discard: Mettre au rebut
7
+ force_discard: Forcer le rejet
7
8
  inspect: Inspecter
8
9
  reschedule: Reprogrammer
9
10
  retry: Recommencez
@@ -110,10 +111,14 @@ fr:
110
111
  actions:
111
112
  confirm_destroy: Voulez-vous vraiment détruire le job ?
112
113
  confirm_discard: Voulez-vous vraiment mettre au rebut le job ?
114
+ confirm_force_discard: 'Êtes-vous sûr de vouloir forcer l''abandon de cette tâche ? Le travail sera marqué comme abandonné mais le travail en cours d''exécution ne sera pas arrêté - il ne sera cependant pas réessayé en cas d''échec.
115
+
116
+ '
113
117
  confirm_reschedule: Voulez-vous vraiment replanifier le job ?
114
118
  confirm_retry: Voulez-vous vraiment réessayer le job ?
115
119
  destroy: Détruire le job
116
120
  discard: Mettre au rebut le job
121
+ force_discard: Forcer l'abandon du travail
117
122
  reschedule: Replanifier le job
118
123
  retry: Réessayer le job
119
124
  destroy:
@@ -124,6 +129,8 @@ fr:
124
129
  in_queue: Dans la file d'attente
125
130
  runtime: Durée
126
131
  title: Exécutions
132
+ force_discard:
133
+ notice: Le travail a été abandonné de force. Il continuera à fonctionner mais il ne sera pas réessayé en cas d'échec
127
134
  index:
128
135
  job_pagination: Pagination du job
129
136
  older_jobs: Jobs plus anciens
@@ -4,6 +4,7 @@ ja:
4
4
  actions:
5
5
  destroy: 削除する
6
6
  discard: 破棄する
7
+ force_discard: 強制破棄
7
8
  inspect: 調査する
8
9
  reschedule: 再スケジュールする
9
10
  retry: 再試行する
@@ -110,10 +111,14 @@ ja:
110
111
  actions:
111
112
  confirm_destroy: ジョブを削除してもよろしいですか?
112
113
  confirm_discard: ジョブを破棄してもよろしいですか?
114
+ confirm_force_discard: 'このジョブを強制的に破棄してもよろしいですか?ジョブは破棄済みとしてマークされますが、実行中のジョブは停止されません。ただし、失敗した場合は再試行されません。
115
+
116
+ '
113
117
  confirm_reschedule: ジョブを再スケジュールしてもよろしいですか?
114
118
  confirm_retry: ジョブを再試行してもよろしいですか?
115
119
  destroy: ジョブを削除
116
120
  discard: ジョブを破棄
121
+ force_discard: ジョブを強制的に破棄する
117
122
  reschedule: ジョブを再スケジュール
118
123
  retry: ジョブを再試行
119
124
  destroy:
@@ -124,6 +129,8 @@ ja:
124
129
  in_queue: 待機中
125
130
  runtime: 実行時間
126
131
  title: 実行
132
+ force_discard:
133
+ notice: ジョブは強制的に破棄されました。実行は継続されますが、失敗した場合は再試行されません
127
134
  index:
128
135
  job_pagination: ジョブのページネーション
129
136
  older_jobs: 古いジョブ
@@ -4,6 +4,7 @@ nl:
4
4
  actions:
5
5
  destroy: Vernietigen
6
6
  discard: Weggooien
7
+ force_discard: Forceer weggooien
7
8
  inspect: Inspecteren
8
9
  reschedule: Opnieuw plannen
9
10
  retry: Opnieuw proberen
@@ -110,10 +111,14 @@ nl:
110
111
  actions:
111
112
  confirm_destroy: Weet je zeker dat je de baan wilt vernietigen?
112
113
  confirm_discard: Wilt u de baan afwijzen?
114
+ confirm_force_discard: 'Weet u zeker dat u deze taak geforceerd wilt weggooien? De taak wordt gemarkeerd als verwijderd, maar de lopende taak wordt niet gestopt. Bij fouten wordt deze echter niet opnieuw geprobeerd.
115
+
116
+ '
113
117
  confirm_reschedule: Weet u zeker dat u de taak opnieuw wilt inplannen?
114
118
  confirm_retry: Weet u zeker dat u de taak opnieuw wilt proberen?
115
119
  destroy: Baan vernietigen
116
120
  discard: Gooi de baan weg
121
+ force_discard: Forceer taak weggooien
117
122
  reschedule: Taak opnieuw plannen
118
123
  retry: Taak opnieuw proberen
119
124
  destroy:
@@ -124,6 +129,8 @@ nl:
124
129
  in_queue: in de wachtrij
125
130
  runtime: looptijd
126
131
  title: Executies
132
+ force_discard:
133
+ notice: Baan is gedwongen verwijderd. Het blijft actief, maar wordt niet opnieuw geprobeerd als er fouten optreden
127
134
  index:
128
135
  job_pagination: Taak paginering
129
136
  older_jobs: Oudere banen
@@ -4,6 +4,7 @@ ru:
4
4
  actions:
5
5
  destroy: Разрушать
6
6
  discard: Отказаться
7
+ force_discard: Принудительно отменить
7
8
  inspect: Осмотреть
8
9
  reschedule: Перенести
9
10
  retry: Повторить попытку
@@ -134,10 +135,14 @@ ru:
134
135
  actions:
135
136
  confirm_destroy: Вы уверены, что хотите уничтожить задание?
136
137
  confirm_discard: Вы уверены, что хотите отказаться от задания?
138
+ confirm_force_discard: 'Вы уверены, что хотите принудительно отменить это задание? Задание будет помечено как отброшенное, но выполняемое задание не будет остановлено, однако в случае сбоя оно не будет повторено.
139
+
140
+ '
137
141
  confirm_reschedule: Вы уверены, что хотите перенести задание?
138
142
  confirm_retry: Вы уверены, что хотите повторить задание?
139
143
  destroy: Уничтожить работу
140
144
  discard: Отменить задание
145
+ force_discard: Принудительно отменить задание
141
146
  reschedule: Перенести задание
142
147
  retry: Повторить задание
143
148
  destroy:
@@ -148,6 +153,8 @@ ru:
148
153
  in_queue: в очереди
149
154
  runtime: время выполнения
150
155
  title: Казни
156
+ force_discard:
157
+ notice: Иов был принудительно отброшен. Он продолжит работу, но не будет повторяться в случае сбоя.
151
158
  index:
152
159
  job_pagination: Пагинация вакансий
153
160
  older_jobs: Старые рабочие места
@@ -4,6 +4,7 @@ tr:
4
4
  actions:
5
5
  destroy: Sil
6
6
  discard: İptal Et
7
+ force_discard: Atmaya zorla
7
8
  inspect: İncele
8
9
  reschedule: Yeniden planla
9
10
  retry: Tekrar dene
@@ -110,10 +111,14 @@ tr:
110
111
  actions:
111
112
  confirm_destroy: Bu işi silmek istediğinizden emin misiniz?
112
113
  confirm_discard: Bu işi iptal etmek istediğinizden emin misiniz?
114
+ confirm_force_discard: 'Bu işi zorla iptal etmek istediğinizden emin misiniz? İş atıldı olarak işaretlenecek ancak devam eden iş durdurulmayacak; ancak başarısızlık durumunda yeniden denenmeyecek.
115
+
116
+ '
113
117
  confirm_reschedule: Bu işi yeniden planlamak istediğinizden emin misiniz?
114
118
  confirm_retry: Bu işi tekrar denemek istediğinizden emin misiniz?
115
119
  destroy: İşi Sil
116
120
  discard: İşi İptal Et
121
+ force_discard: İşi zorla atmaya zorla
117
122
  reschedule: İşi Yeniden Planla
118
123
  retry: İşi Tekrar Dene
119
124
  destroy:
@@ -124,6 +129,8 @@ tr:
124
129
  in_queue: sırada
125
130
  runtime: çalışma süresi
126
131
  title: İşlemler
132
+ force_discard:
133
+ notice: İş zorla atıldı. Çalışmaya devam edecek ancak arıza durumunda yeniden denenmeyecek
127
134
  index:
128
135
  job_pagination: İş sayfalandırması
129
136
  older_jobs: Daha eski işler
@@ -4,6 +4,7 @@ uk:
4
4
  actions:
5
5
  destroy: Видалити
6
6
  discard: Відхилити
7
+ force_discard: Примусово скинути
7
8
  inspect: Оглянути
8
9
  reschedule: Перепланувати
9
10
  retry: Повторити
@@ -134,10 +135,14 @@ uk:
134
135
  actions:
135
136
  confirm_destroy: Ви впевнені, що хочете видалити завдання?
136
137
  confirm_discard: Ви впевнені, що хочете відхилити завдання?
138
+ confirm_force_discard: 'Ви впевнені, що хочете примусово скасувати цю роботу? Завдання буде позначено як відхилене, але виконуване завдання не буде зупинено – однак його не буде повторено у випадку помилок.
139
+
140
+ '
137
141
  confirm_reschedule: Ви впевнені, що хочете перепланувати завдання?
138
142
  confirm_retry: Ви впевнені, що хочете повторити завдання?
139
143
  destroy: Видалити завдання
140
144
  discard: Відхилити завдання
145
+ force_discard: Примусово скасувати завдання
141
146
  reschedule: Перепланувати завдання
142
147
  retry: Повторити завдання
143
148
  destroy:
@@ -148,6 +153,8 @@ uk:
148
153
  in_queue: у черзі
149
154
  runtime: час виконання
150
155
  title: Виконання
156
+ force_discard:
157
+ notice: Роботу примусово скасовано. Він продовжуватиме працювати, але не буде повторюватися в разі помилок
151
158
  index:
152
159
  job_pagination: Пагінація робіт
153
160
  older_jobs: Старі роботи
data/config/routes.rb CHANGED
@@ -11,6 +11,7 @@ GoodJob::Engine.routes.draw do
11
11
 
12
12
  member do
13
13
  put :discard
14
+ put :force_discard
14
15
  put :reschedule
15
16
  put :retry
16
17
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '3.18.3'
5
+ VERSION = '3.19.0'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.18.3
4
+ version: 3.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-16 00:00:00.000000000 Z
11
+ date: 2023-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -313,6 +313,7 @@ files:
313
313
  - app/views/good_job/shared/icons/_clock.html.erb
314
314
  - app/views/good_job/shared/icons/_dash_circle.html.erb
315
315
  - app/views/good_job/shared/icons/_dots.html.erb
316
+ - app/views/good_job/shared/icons/_eject.html.erb
316
317
  - app/views/good_job/shared/icons/_exclamation.html.erb
317
318
  - app/views/good_job/shared/icons/_info.html.erb
318
319
  - app/views/good_job/shared/icons/_moon_stars_fill.html.erb