Class: Flightdeck::JobRow

Inherits:
Object
  • Object
show all
Defined in:
app/models/flightdeck/job_row.rb

Overview

One row of a jobs list. Deliberately dumb: JobsQuery assembles these from bounded queries and the view asks them formatting questions.

Constant Summary collapse

EXECUTION_MODELS =

Every table a job can be sitting in, in the order we report them. A job is only ever in one of these at a time, but we probe all five so a row is never silently mislabelled during a transition.

{
  ready: "SolidQueue::ReadyExecution",
  scheduled: "SolidQueue::ScheduledExecution",
  in_progress: "SolidQueue::ClaimedExecution",
  blocked: "SolidQueue::BlockedExecution",
  failed: "SolidQueue::FailedExecution"
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(job:, state:, execution: nil, args_preview: nil, error_summary: nil, process: nil) ⇒ JobRow

Returns a new instance of JobRow.



58
59
60
61
62
63
64
65
# File 'app/models/flightdeck/job_row.rb', line 58

def initialize(job:, state:, execution: nil, args_preview: nil, error_summary: nil, process: nil)
  @job = job
  @state = state
  @execution = execution
  @args_preview = args_preview
  @error_summary = error_summary || ErrorSummary.none
  @process = process
end

Instance Attribute Details

#args_previewObject (readonly)

Returns the value of attribute args_preview.



56
57
58
# File 'app/models/flightdeck/job_row.rb', line 56

def args_preview
  @args_preview
end

#error_summaryObject (readonly)

Returns the value of attribute error_summary.



56
57
58
# File 'app/models/flightdeck/job_row.rb', line 56

def error_summary
  @error_summary
end

#executionObject (readonly)

Returns the value of attribute execution.



56
57
58
# File 'app/models/flightdeck/job_row.rb', line 56

def execution
  @execution
end

#jobObject (readonly)

Returns the value of attribute job.



56
57
58
# File 'app/models/flightdeck/job_row.rb', line 56

def job
  @job
end

#processObject (readonly)

Returns the value of attribute process.



56
57
58
# File 'app/models/flightdeck/job_row.rb', line 56

def process
  @process
end

#stateObject (readonly)

Returns the value of attribute state.



56
57
58
# File 'app/models/flightdeck/job_row.rb', line 56

def state
  @state
end

Class Method Details

.annotate(job_ids) ⇒ Object

Resolves the state of an already-loaded page of jobs with one WHERE job_id IN (page_ids) lookup per executions table. Returns { job_id => { state:, execution_id:, process_id: } }.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'app/models/flightdeck/job_row.rb', line 37

def annotate(job_ids)
  job_ids = Array(job_ids).compact.uniq
  return {} if job_ids.empty?

  EXECUTION_MODELS.each_with_object({}) do |(state, model_name), found|
    model = model_name.constantize
    columns = [ :id, :job_id ]
    columns << :process_id if model.column_names.include?("process_id")

    model.where(job_id: job_ids).pluck(*columns).each do |values|
      job_id = values[1]
      next if found.key?(job_id)

      found[job_id] = { state: state, execution_id: values[0], process_id: values[2] }
    end
  end
end

.model_for(state) ⇒ Object



19
20
21
22
# File 'app/models/flightdeck/job_row.rb', line 19

def model_for(state)
  name = EXECUTION_MODELS[state.to_sym]
  name && name.constantize
end

.state_for(job, annotation) ⇒ Object

How a job's state is derived, in one place: finished_at wins, then whichever execution table annotate found the job in, then :unknown for the transition window where a job is between tables.



27
28
29
30
31
32
# File 'app/models/flightdeck/job_row.rb', line 27

def state_for(job, annotation)
  return :finished if job.finished_at.present?
  return annotation[:state] if annotation

  :unknown
end

Instance Method Details

#active_job_idObject



71
# File 'app/models/flightdeck/job_row.rb', line 71

def active_job_id = job.active_job_id

#attemptsObject

Attempt number, when ActiveJob recorded one in the serialized payload. We only ever see a truncated prefix of the arguments, so this is best-effort by design and returns nil rather than guessing.



98
99
100
101
# File 'app/models/flightdeck/job_row.rb', line 98

def attempts
  value = args_preview.to_s[/"executions"\s*:\s*(\d+)/, 1]
  value && value.to_i + 1
end

#class_nameObject



68
# File 'app/models/flightdeck/job_row.rb', line 68

def class_name = job.class_name

#concurrency_keyObject



72
# File 'app/models/flightdeck/job_row.rb', line 72

def concurrency_key = job.concurrency_key

#discardable?Boolean

Returns:

  • (Boolean)


78
# File 'app/models/flightdeck/job_row.rb', line 78

def discardable? = state != :in_progress

#due_atObject

The moment the job first became eligible to run — what "waiting" is measured from, so a job scheduled for next week is not reported as having waited a week.



83
84
85
# File 'app/models/flightdeck/job_row.rb', line 83

def due_at
  scheduled_at || enqueued_at
end

#enqueued_atObject



73
# File 'app/models/flightdeck/job_row.rb', line 73

def enqueued_at = job.created_at

#execution_started_atObject



87
88
89
# File 'app/models/flightdeck/job_row.rb', line 87

def execution_started_at
  execution&.created_at if state == :in_progress
end

#failed?Boolean

Returns:

  • (Boolean)


77
# File 'app/models/flightdeck/job_row.rb', line 77

def failed? = state == :failed

#failed_atObject



91
92
93
# File 'app/models/flightdeck/job_row.rb', line 91

def failed_at
  execution&.created_at if failed?
end

#finished_atObject



75
# File 'app/models/flightdeck/job_row.rb', line 75

def finished_at = job.finished_at

#idObject



67
# File 'app/models/flightdeck/job_row.rb', line 67

def id = job.id

#priorityObject



70
# File 'app/models/flightdeck/job_row.rb', line 70

def priority = job.priority

#queue_nameObject



69
# File 'app/models/flightdeck/job_row.rb', line 69

def queue_name = job.queue_name

#scheduled_atObject



74
# File 'app/models/flightdeck/job_row.rb', line 74

def scheduled_at = job.scheduled_at