Module: Sidekiq::Status::Web

Defined in:
lib/sidekiq-status/web.rb

Overview

Hook into Sidekiq::Web Sinatra app which adds a new “/statuses” page

Constant Summary collapse

VIEW_PATH =

Location of Sidekiq::Status::Web view templates

File.expand_path('../../../web/views', __FILE__)
DEFAULT_PER_PAGE_OPTS =
[25, 50, 100].freeze
DEFAULT_PER_PAGE =
25
COMMON_STATUS_HASH_KEYS =
%w(update_time jid status worker args label pct_complete total at message working_at elapsed eta)

Class Method Summary collapse

Class Method Details

.default_per_pageObject



23
24
25
# File 'lib/sidekiq-status/web.rb', line 23

def default_per_page
  @default_per_page || DEFAULT_PER_PAGE
end

.default_per_page=(val) ⇒ Object



20
21
22
# File 'lib/sidekiq-status/web.rb', line 20

def default_per_page= val
  @default_per_page = val
end

.per_page_optsObject



17
18
19
# File 'lib/sidekiq-status/web.rb', line 17

def per_page_opts
  @per_page_opts || DEFAULT_PER_PAGE_OPTS
end

.per_page_opts=(arr) ⇒ Object



14
15
16
# File 'lib/sidekiq-status/web.rb', line 14

def per_page_opts= arr
  @per_page_opts = arr
end

.registered(app) ⇒ Object

Parameters:

  • app (Sidekiq::Web)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/sidekiq-status/web.rb', line 29

def self.registered(app)

  # Allow method overrides to support RESTful deletes
  app.set :method_override, true

  app.helpers do
    def csrf_tag
      "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
    end

    def poll_path
      "?#{request.query_string}" if params[:poll]
    end

    def sidekiq_status_template(name)
      path = File.join(VIEW_PATH, name.to_s) + ".erb"
      File.open(path).read
    end

    def add_details_to_status(status)
      status['label'] = status_label(status['status'])
      status["pct_complete"] ||= pct_complete(status)
      status["elapsed"] ||= elapsed(status).to_s
      status["eta"] ||= eta(status).to_s
      status["custom"] = process_custom_data(status)
      return status
    end

    def process_custom_data(hash)
      hash.reject { |key, _| COMMON_STATUS_HASH_KEYS.include?(key) }
    end

    def pct_complete(status)
      return 100 if status['status'] == 'complete'
      Sidekiq::Status::pct_complete(status['jid']) || 0
    end

    def elapsed(status)
      case status['status']
      when 'complete'
        Sidekiq::Status.update_time(status['jid']) - Sidekiq::Status.working_at(status['jid'])
      when 'working', 'retrying'
        Time.now.to_i - Sidekiq::Status.working_at(status['jid'])
      end
    end

    def eta(status)
      Sidekiq::Status.eta(status['jid']) if status['status'] == 'working'
    end

    def status_label(status)
      case status
      when 'complete'
        'success'
      when 'working', 'retrying'
        'warning'
      when 'queued'
        'primary'
      else
        'danger'
      end
    end

    def has_sort_by?(value)
      ["worker", "status", "update_time", "pct_complete", "message", "args"].include?(value)
    end
  end

  app.get '/statuses' do

    jids = Sidekiq::Status.redis_adapter do |conn|
      conn.scan(match: 'sidekiq:status:*', count: 100).map do |key|
        key.split(':').last
      end.uniq
    end
    @statuses = []

    jids.each do |jid|
      status = Sidekiq::Status::get_all jid
      next if !status || status.count < 2
      status = add_details_to_status(status)
      @statuses << status
    end

    sort_by = has_sort_by?(params[:sort_by]) ? params[:sort_by] : "update_time"
    sort_dir = "asc"

    if params[:sort_dir] == "asc"
      @statuses = @statuses.sort { |x,y| (x[sort_by] <=> y[sort_by]) || -1 }
    else
      sort_dir = "desc"
      @statuses = @statuses.sort { |y,x| (x[sort_by] <=> y[sort_by]) || 1 }
    end

    if params[:status] && params[:status] != "all"
      @statuses = @statuses.select {|job_status| job_status["status"] == params[:status] }
    end

    # Sidekiq pagination
    @total_size = @statuses.count
    @count = params[:per_page] ? params[:per_page].to_i : Sidekiq::Status::Web.default_per_page
    @count = @total_size if params[:per_page] == 'all'
    @current_page = params[:page].to_i < 1 ? 1 : params[:page].to_i
    @statuses = @statuses.slice((@current_page - 1) * @count, @count)

    @headers = [
      {id: "worker", name: "Worker / JID", class: nil, url: nil},
      {id: "args", name: "Arguments", class: nil, url: nil},
      {id: "status", name: "Status", class: nil, url: nil},
      {id: "update_time", name: "Last Updated", class: nil, url: nil},
      {id: "pct_complete", name: "Progress", class: nil, url: nil},
      {id: "elapsed", name: "Time Elapsed", class: nil, url: nil},
      {id: "eta", name: "ETA", class: nil, url: nil},
    ]

    @headers.each do |h|
      h[:url] = "statuses?" + params.merge("sort_by" => h[:id], "sort_dir" => (sort_by == h[:id] && sort_dir == "asc") ? "desc" : "asc").map{|k, v| "#{k}=#{CGI.escape v.to_s}"}.join("&")
      h[:class] = "sorted_#{sort_dir}" if sort_by == h[:id]
    end

    erb(sidekiq_status_template(:statuses))
  end

  app.get '/statuses/:jid' do
    job = Sidekiq::Status::get_all params['jid']

    if job.empty?
      throw :halt, [404, {"Content-Type" => "text/html"}, [erb(sidekiq_status_template(:status_not_found))]]
    else
      @status = add_details_to_status(job)
      erb(sidekiq_status_template(:status))
    end
  end

  # Retries a failed job from the status list
  app.put '/statuses' do
    job = Sidekiq::RetrySet.new.find_job(params[:jid])
    job ||= Sidekiq::DeadSet.new.find_job(params[:jid])
    job.retry if job
    throw :halt, [302, { "Location" => request.referer }, []]
  end

  # Removes a completed job from the status list
  app.delete '/statuses' do
    Sidekiq::Status.delete(params[:jid])
    throw :halt, [302, { "Location" => request.referer }, []]
  end
end