Class: OpenLoam::ProgressJob
- Inherits:
-
TenantRecord
- Object
- TenantRecord
- OpenLoam::ProgressJob
- Defined in:
- app/models/open_loam/progress_job.rb
Overview
Progress of a long-running job (a bulk import, a reindex, a report) so the admin can watch it live instead of guessing. Tenant-scoped; a job started in one tenant is only visible and streamable there.
Deliberately NOT audited: progress is high-frequency churn (many advance
ticks) and the audit trail would fill with noise. The terminal status and
any error are captured on the row itself, which is the summary that matters.
Each meaningful change publishes open_loam.progress.updated (broadcastable → SSE)
carrying only id/percent/status — no record contents. See OpenLoam::Progress for
the entry point.
Constant Summary collapse
- STATUSES =
%w[running completed failed cancelled].freeze
- STALE_AFTER =
5.minutes
Instance Method Summary collapse
-
#advance(by: 1, message: nil) ⇒ Object
Increment progress.
- #cancel! ⇒ Object
-
#cancelled? ⇒ Boolean
A cooperative cancel: a long job calls this periodically and stops early.
- #complete! ⇒ Object
-
#eta_seconds ⇒ Object
Seconds remaining, extrapolated from the rate so far, or nil when it can't be estimated yet (no progress, or already finished).
- #fail!(error_message = nil) ⇒ Object
-
#percent ⇒ Object
Integer 0..100.
- #running? ⇒ Boolean
-
#stale? ⇒ Boolean
A running job whose process died leaves the row "running" forever; it is stale once its heartbeat (updated_at, bumped on every advance) goes quiet.
- #terminal? ⇒ Boolean
Instance Method Details
#advance(by: 1, message: nil) ⇒ Object
Increment progress. Persists every tick (so the count and heartbeat stay current) but THROTTLES the SSE broadcast to once per whole percent (or a message change) — a 10k-item job pushes ~100 frames, not 10k.
57 58 59 60 61 62 63 64 |
# File 'app/models/open_loam/progress_job.rb', line 57 def advance(by: 1, message: nil) before = percent self.completed = completed.to_i + by self. = if save! broadcast if percent != before || self end |
#cancel! ⇒ Object
74 75 76 |
# File 'app/models/open_loam/progress_job.rb', line 74 def cancel! finish!("cancelled") end |
#cancelled? ⇒ Boolean
A cooperative cancel: a long job calls this periodically and stops early. Re-reads the status column so an admin's cancel in ANOTHER request is seen without clobbering the in-memory counters.
81 82 83 |
# File 'app/models/open_loam/progress_job.rb', line 81 def cancelled? self.class.where(id: id).pick(:status) == "cancelled" end |
#complete! ⇒ Object
66 67 68 |
# File 'app/models/open_loam/progress_job.rb', line 66 def complete! finish!("completed") { self.completed = total if total.to_i.positive? } end |
#eta_seconds ⇒ Object
Seconds remaining, extrapolated from the rate so far, or nil when it can't be estimated yet (no progress, or already finished).
36 37 38 39 40 41 42 43 44 |
# File 'app/models/open_loam/progress_job.rb', line 36 def eta_seconds return nil unless running? && completed.to_i.positive? && started_at elapsed = Time.current - started_at rate = completed / elapsed # items per second return nil unless rate.positive? ((total - completed) / rate).round end |
#fail!(error_message = nil) ⇒ Object
70 71 72 |
# File 'app/models/open_loam/progress_job.rb', line 70 def fail!( = nil) finish!("failed") { self.error = .to_s.presence } end |
#percent ⇒ Object
Integer 0..100. A zero total is treated as 0% (an unknown-size job).
25 26 27 28 29 |
# File 'app/models/open_loam/progress_job.rb', line 25 def percent return 0 if total.to_i <= 0 [ (completed.to_f / total * 100).floor, 100 ].min end |
#running? ⇒ Boolean
31 |
# File 'app/models/open_loam/progress_job.rb', line 31 def running? = status == "running" |
#stale? ⇒ Boolean
A running job whose process died leaves the row "running" forever; it is stale once its heartbeat (updated_at, bumped on every advance) goes quiet. The prototype exposes the predicate and a manual mark-failed; a reaper daemon is the roadmap.
50 51 52 |
# File 'app/models/open_loam/progress_job.rb', line 50 def stale? running? && updated_at < STALE_AFTER.ago end |
#terminal? ⇒ Boolean
32 |
# File 'app/models/open_loam/progress_job.rb', line 32 def terminal? = !running? |