Class: Flightdeck::JobDetail

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

Overview

Everything the job show page needs, assembled from Solid Queue's own models.

Arguments are shown exactly as they were stored. They are never handed to ActiveJob for deserialization: that would load host application classes (and run their code) just to render a dashboard.

Defined Under Namespace

Classes: Event

Constant Summary collapse

ARGUMENTS_DISPLAY_LIMIT =
10.kilobytes
VENDOR_FRAME =

A frame belongs to the application unless it lives in an installed gem or in Ruby's own library directories.

%r{
  (?:\A|/)(?:gems|ruby|rubygems|bundler|vendor/bundle)/ |
  \A<internal: | \A/usr/lib/ruby/
}x

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(job:, annotation: nil) ⇒ JobDetail

Returns a new instance of JobDetail.



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

def initialize(job:, annotation: nil)
  @job = job
  @state = JobRow.state_for(job, annotation)
  @execution = load_execution(annotation)
  @process = load_process
end

Instance Attribute Details

#executionObject (readonly)

Returns the value of attribute execution.



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

def execution
  @execution
end

#jobObject (readonly)

Returns the value of attribute job.



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

def job
  @job
end

#processObject (readonly)

Returns the value of attribute process.



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

def process
  @process
end

#stateObject (readonly)

Returns the value of attribute state.



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

def state
  @state
end

Class Method Details

.find(id) ⇒ Object



21
22
23
24
25
26
# File 'app/models/flightdeck/job_detail.rb', line 21

def self.find(id)
  job = SolidQueue::Job.find(id)
  annotation = JobRow.annotate([ job.id ])[job.id]

  new(job: job, annotation: annotation)
end

Instance Method Details

#active_job_idObject



39
# File 'app/models/flightdeck/job_detail.rb', line 39

def active_job_id = job.active_job_id

#app_frame?(line) ⇒ Boolean

Returns:

  • (Boolean)


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

def app_frame?(line)
  !VENDOR_FRAME.match?(line.to_s)
end

#arguments_bytesObject



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

def arguments_bytes = arguments_json.bytesize

#arguments_jsonObject

job.arguments is already a Ruby structure (Solid Queue stores it with a JSON coder); pretty-printing it is a formatting step, not deserialization.



57
58
59
60
61
62
63
# File 'app/models/flightdeck/job_detail.rb', line 57

def arguments_json
  @arguments_json ||= begin
    JSON.pretty_generate(job.arguments)
  rescue StandardError
    job.arguments.to_s
  end
end

#arguments_previewObject



69
70
71
72
73
# File 'app/models/flightdeck/job_detail.rb', line 69

def arguments_preview
  return arguments_json unless arguments_truncated?

  arguments_json.byteslice(0, ARGUMENTS_DISPLAY_LIMIT).scrub("")
end

#arguments_truncated?Boolean

Returns:

  • (Boolean)


65
66
67
# File 'app/models/flightdeck/job_detail.rb', line 65

def arguments_truncated?
  arguments_json.bytesize > ARGUMENTS_DISPLAY_LIMIT
end

#backtraceObject



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

def backtrace
  @backtrace ||= Array(failed_execution&.backtrace).first(Flightdeck.config.backtrace_lines)
end

#backtrace_framesObject



95
96
97
# File 'app/models/flightdeck/job_detail.rb', line 95

def backtrace_frames
  backtrace.map { |line| { line: line, app: app_frame?(line) } }
end

#backtrace_totalObject



91
# File 'app/models/flightdeck/job_detail.rb', line 91

def backtrace_total = Array(failed_execution&.backtrace).size

#backtrace_truncated?Boolean

Returns:

  • (Boolean)


93
# File 'app/models/flightdeck/job_detail.rb', line 93

def backtrace_truncated? = backtrace_total > backtrace.size

#class_nameObject



36
# File 'app/models/flightdeck/job_detail.rb', line 36

def class_name = job.class_name

#concurrency_keyObject



40
# File 'app/models/flightdeck/job_detail.rb', line 40

def concurrency_key = job.concurrency_key

#discardable?Boolean

Returns:

  • (Boolean)


47
# File 'app/models/flightdeck/job_detail.rb', line 47

def discardable? = state != :in_progress && state != :finished

#enqueued_atObject



41
# File 'app/models/flightdeck/job_detail.rb', line 41

def enqueued_at = job.created_at

#error_classObject

--- error --------------------------------------------------------------



79
80
81
# File 'app/models/flightdeck/job_detail.rb', line 79

def error_class
  failed_execution&.exception_class
end

#error_messageObject



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

def error_message
  failed_execution&.message
end

#failed?Boolean

Returns:

  • (Boolean)


45
# File 'app/models/flightdeck/job_detail.rb', line 45

def failed? = state == :failed

#failed_executionObject



49
50
51
# File 'app/models/flightdeck/job_detail.rb', line 49

def failed_execution
  execution if failed?
end

#finished_atObject



43
# File 'app/models/flightdeck/job_detail.rb', line 43

def finished_at = job.finished_at

#idObject



35
# File 'app/models/flightdeck/job_detail.rb', line 35

def id = job.id

#priorityObject



38
# File 'app/models/flightdeck/job_detail.rb', line 38

def priority = job.priority

#process_labelObject



136
137
138
139
140
# File 'app/models/flightdeck/job_detail.rb', line 136

def process_label
  return nil unless process

  [ process.name, ("pid #{process.pid}" if process.pid) ].compact.join(" ยท ")
end

#queue_nameObject



37
# File 'app/models/flightdeck/job_detail.rb', line 37

def queue_name = job.queue_name

#retryable?Boolean

Returns:

  • (Boolean)


46
# File 'app/models/flightdeck/job_detail.rb', line 46

def retryable? = failed?

#scheduled_atObject



42
# File 'app/models/flightdeck/job_detail.rb', line 42

def scheduled_at = job.scheduled_at

#timelineObject

Built only from timestamps Solid Queue actually keeps. Steps whose row has already been consumed (a finished job's claim, say) are simply absent rather than invented.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/models/flightdeck/job_detail.rb', line 110

def timeline
  events = [ Event.new(label: "Enqueued", at: enqueued_at, detail: "queue #{queue_name}", status: :ok) ]

  if scheduled_at.present? && scheduled_at > enqueued_at
    events << Event.new(label: "Scheduled", at: scheduled_at,
                        detail: scheduled_at.future? ? "not yet due" : "became due", status: :ok)
  end

  case state
  when :ready
    events << Event.new(label: "Ready", at: execution&.created_at, detail: "waiting for a worker", status: :now)
  when :blocked
    events << Event.new(label: "Blocked", at: execution&.created_at,
                        detail: "concurrency key #{job.concurrency_key}", status: :now)
  when :in_progress
    events << Event.new(label: "Claimed", at: execution&.created_at, detail: process_label, status: :now)
  when :failed
    events << Event.new(label: "Failed", at: execution&.created_at, detail: error_class, status: :bad)
    events << Event.new(label: "Awaiting decision", at: nil,
                        detail: "retry or discard", status: :now)
  end

  events << Event.new(label: "Finished", at: finished_at, detail: nil, status: :ok) if finished_at.present?
  events.select { |event| event.at.present? || event.status == :now }
end