que-view 0.3.1 → 0.3.2

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: d07972f8adf809f3f10f495824376c64724e1526e7afd6d85a05da93632741e0
4
- data.tar.gz: 95d6ce6e4d1340dcb550a1ae4b85560f19a18de0b9c0dec1eea5ce5a44726ddf
3
+ metadata.gz: 1b7b44890b63b063c130537e5fcc117f40bf1236dd89b156ca48e87a78d71248
4
+ data.tar.gz: 629fd230eb265fdbe7a41f0aec19e37f4047d797750eb635612687cceb7d5af9
5
5
  SHA512:
6
- metadata.gz: 9553edb70775fc2fa268ad2ec3e14d3ab51c3858b54363efbb0d6b8aae85f00d450d99e925cd576dc7117f1ff784bbbe664d763305123598acc35bda57e38e8b
7
- data.tar.gz: ce8bc2cbfef7844814f60125bbad375bf676bc2ef14180d5a2bdc2b4a9fd5cc3ac1920f5b5f40b540a2e600ee4107c247c3b89f90d3e7662725de1a6539c2461
6
+ metadata.gz: 1412a8a378aaa977fd92b40c79dad153267cbf50d1e7ac7409380469106269e5b96484d12193ac57ee78187ad172e79a61783e54e0948aff9242e4947bf6d381
7
+ data.tar.gz: ac32754839e4105584d330cc34a19cb5fccb48f8ce60afd58c1c3f5bb9b933a7ab4bd8616e109d7a2a5b91b384dff878b4a5699ee576f5d5fa1822c60d4392b0
data/README.md CHANGED
@@ -4,12 +4,13 @@ SQL queries came from Que::Web, some styling from there too.
4
4
 
5
5
  Benefits for using this one:
6
6
  - no Sinatra (que-web based on Sinatra),
7
- - no Foundation for styles,
8
- - no jQuery,
7
+ - no Foundation for styles (and no external styles at all),
8
+ - no jQuery (and no java scripts at all),
9
9
  - Que::Web was last updated 2 years ago.
10
10
 
11
- <img width="1735" alt="Снимок экрана 2024-02-21 в 15 10 35" src="https://github.com/kortirso/que-view/assets/6195394/cd2812c7-abb0-48d9-92d5-4dbef93bcd9e">
12
- <img width="1735" alt="Снимок экрана 2024-02-21 в 15 11 12" src="https://github.com/kortirso/que-view/assets/6195394/8af01e7f-a002-4ef1-aeff-f96fd27c639f">
11
+ <img width="1731" alt="Снимок экрана 2024-03-03 в 08 28 09" src="https://github.com/kortirso/que-view/assets/6195394/fdaf315d-6c6e-40ee-a60f-cd740cc7ec93">
12
+ <img width="1734" alt="Снимок экрана 2024-03-03 в 08 27 47" src="https://github.com/kortirso/que-view/assets/6195394/e45d334e-7637-41f8-af16-d6a9ac35f263">
13
+ <img width="1730" alt="Снимок экрана 2024-03-03 в 23 09 57" src="https://github.com/kortirso/que-view/assets/6195394/d790066f-3e96-4775-afd6-018750d0afd3">
13
14
 
14
15
 
15
16
  ## Installation
@@ -29,6 +29,10 @@ body {
29
29
  background: #fafafa;
30
30
  }
31
31
 
32
+ h1 {
33
+ margin-top: 0;
34
+ }
35
+
32
36
  .row {
33
37
  display: flex;
34
38
  }
@@ -112,7 +116,7 @@ body {
112
116
  }
113
117
 
114
118
  .pagination {
115
- margin: 1rem auto;
119
+ margin: 0 auto 1rem;
116
120
  }
117
121
 
118
122
  .pagination .pagination-link {
@@ -181,6 +185,10 @@ body {
181
185
  background: #e7e5e4;
182
186
  }
183
187
 
188
+ .search-form {
189
+ margin-bottom: 1rem;
190
+ }
191
+
184
192
  .search-form .form-select {
185
193
  margin-right: 1rem;
186
194
  padding: .5rem 1rem;
@@ -221,6 +229,19 @@ table p {
221
229
  margin: 0;
222
230
  }
223
231
 
232
+ .lockers {
233
+ margin-top: 2rem;
234
+ }
235
+
236
+ .lockers .locker-process {
237
+ display: flex;
238
+ flex-direction: column;
239
+ }
240
+
241
+ .metrics a {
242
+ color: #000;
243
+ }
244
+
224
245
  @media screen and (max-width: 768px) {
225
246
  .dashboard-row {
226
247
  flex-direction: column;
@@ -84,7 +84,7 @@ module Que
84
84
 
85
85
  def find_jobs(params)
86
86
  case params[:status]&.to_sym
87
- when :running then ::Que::View.fetch_running_jobs(params)
87
+ when :running then ::Que::View.fetch_running_jobs
88
88
  when :failing then ::Que::View.fetch_failing_jobs(PER_PAGE, offset, params)
89
89
  when :scheduled then ::Que::View.fetch_scheduled_jobs(PER_PAGE, offset, params)
90
90
  when :finished then ::Que::View.fetch_finished_jobs(PER_PAGE, offset, params)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Que
4
+ module View
5
+ class QueueMetricsController < Que::View::ApplicationController
6
+ before_action :find_queue_metrics, only: %i[index]
7
+
8
+ def index; end
9
+
10
+ private
11
+
12
+ def find_queue_metrics
13
+ @queue_metrics = ::Que::View.fetch_queue_metrics
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,6 +5,7 @@ module Que
5
5
  class WelcomeController < Que::View::ApplicationController
6
6
  def index
7
7
  @dashboard_stats = ::Que::View.fetch_dashboard_stats[0]
8
+ @lockers = ::Que::View.fetch_que_lockers
8
9
  end
9
10
  end
10
11
  end
@@ -26,6 +26,9 @@
26
26
  <li class="<%= 'active' if params[:status] == 'expired' %>">
27
27
  <%= link_to 'Expired', jobs_path(status: 'expired') %>
28
28
  </li>
29
+ <li class="<%= 'active' if current_page?(queue_metrics_path) %>">
30
+ <%= link_to 'Queue metrics', queue_metrics_path %>
31
+ </li>
29
32
  </ul>
30
33
  <ul class="version">
31
34
  <li>Que <%= Que::VERSION %></li>
@@ -1,3 +1,4 @@
1
+ <h1><%= params[:status].capitalize %> jobs</h1>
1
2
  <%= form_with url: jobs_path, method: :get, class: 'search-form' do |form| %>
2
3
  <%= form.hidden_field :status, value: params[:status] %>
3
4
  <div class="row">
@@ -6,77 +7,73 @@
6
7
  <%= form.submit 'Search', class: 'btn-primary' %>
7
8
  </div>
8
9
  <% end %>
9
- <% if @jobs.any? %>
10
- <% if @pagination %>
11
- <div class="row pagination">
12
- <% if @pagination.previous_page? %>
13
- <%= link_to 'First', jobs_path(status: params[:status], page: 1), class: 'pagination-link' %>
14
- <%= link_to '< Previous', jobs_path(status: params[:status], page: @pagination.previous_page), class: 'pagination-link' %>
15
- <% else %>
16
- <p class="pagination-link disabled">First</p>
17
- <p class="pagination-link disabled">&#60; Previous</p>
10
+ <% if @pagination %>
11
+ <div class="row pagination">
12
+ <% if @pagination.previous_page? %>
13
+ <%= link_to 'First', jobs_path(status: params[:status], page: 1), class: 'pagination-link' %>
14
+ <%= link_to '< Previous', jobs_path(status: params[:status], page: @pagination.previous_page), class: 'pagination-link' %>
15
+ <% else %>
16
+ <p class="pagination-link disabled">First</p>
17
+ <p class="pagination-link disabled">&#60; Previous</p>
18
+ <% end %>
19
+ <p class="total-pages"><%= "Page #{@pagination.page} of #{@pagination.total_pages}" %></p>
20
+ <% if @pagination.next_page? %>
21
+ <%= link_to 'Next >', jobs_path(status: params[:status], page: @pagination.next_page), class: 'pagination-link' %>
22
+ <%= link_to 'Last', jobs_path(status: params[:status], page: @pagination.total_pages), class: 'pagination-link' %>
23
+ <% else %>
24
+ <p class="pagination-link disabled">Next &#62;</p>
25
+ <p class="pagination-link disabled">Last </p>
26
+ <% end %>
27
+ </div>
28
+ <% end %>
29
+ <table cellspacing="0" class="jobs-list">
30
+ <thead>
31
+ <tr>
32
+ <th>ID</th>
33
+ <th>Job</th>
34
+ <th>Queue</th>
35
+ <th>Run at</th>
36
+ <th>Arguments</th>
37
+ <% if params[:status] == 'failing' %>
38
+ <th>Failures</th>
39
+ <th>Error</th>
18
40
  <% end %>
19
- <p class="total-pages"><%= "Page #{@pagination.page} of #{@pagination.total_pages}" %></p>
20
- <% if @pagination.next_page? %>
21
- <%= link_to 'Next >', jobs_path(status: params[:status], page: @pagination.next_page), class: 'pagination-link' %>
22
- <%= link_to 'Last', jobs_path(status: params[:status], page: @pagination.total_pages), class: 'pagination-link' %>
23
- <% else %>
24
- <p class="pagination-link disabled">Next &#62;</p>
25
- <p class="pagination-link disabled">Last </p>
41
+ <% if %w[failing scheduled].include?(params[:status]) %>
42
+ <th>Actions</th>
26
43
  <% end %>
27
- </div>
28
- <% end %>
29
- <table cellspacing="0" class="jobs-list">
30
- <thead>
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ <% @jobs.each do |job| %>
31
48
  <tr>
32
- <th>ID</th>
33
- <th>Job</th>
34
- <th>Queue</th>
35
- <th>Run at</th>
36
- <th>Arguments</th>
49
+ <td><%= link_to job[:id], job_path(job[:id]) %></td>
50
+ <td><%= humanized_job_class(job) %></td>
51
+ <td><%= job[:queue] %></td>
52
+ <td><%= job[:run_at].strftime("%Y-%m-%d %H:%M:%S") %></td>
53
+ <td>
54
+ <% job.dig(:args, 0, :arguments).each do |argument| %>
55
+ <p><%= argument %></p>
56
+ <% end %>
57
+ </td>
37
58
  <% if params[:status] == 'failing' %>
38
- <th>Failures</th>
39
- <th>Error</th>
59
+ <td><%= job[:error_count] %></td>
60
+ <td><%= format_error(job) %></td>
40
61
  <% end %>
41
62
  <% if %w[failing scheduled].include?(params[:status]) %>
42
- <th>Actions</th>
43
- <% end %>
44
- </tr>
45
- </thead>
46
- <tbody>
47
- <% @jobs.each do |job| %>
48
- <tr>
49
- <td><%= link_to job[:id], job_path(job[:id]) %></td>
50
- <td><%= humanized_job_class(job) %></td>
51
- <td><%= job[:queue] %></td>
52
- <td><%= job[:run_at].strftime("%Y-%m-%d %H:%M:%S") %></td>
53
63
  <td>
54
- <% job.dig(:args, 0, :arguments).each do |argument| %>
55
- <p><%= argument %></p>
56
- <% end %>
64
+ <div class="actions">
65
+ <%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
66
+ <%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
67
+ </div>
57
68
  </td>
58
- <% if params[:status] == 'failing' %>
59
- <td><%= job[:error_count] %></td>
60
- <td><%= format_error(job) %></td>
61
- <% end %>
62
- <% if %w[failing scheduled].include?(params[:status]) %>
63
- <td>
64
- <div class="actions">
65
- <%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
66
- <%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
67
- </div>
68
- </td>
69
- <% end %>
70
- </tr>
71
- <% end %>
72
- </tbody>
73
- </table>
69
+ <% end %>
70
+ </tr>
71
+ <% end %>
72
+ </tbody>
73
+ </table>
74
+ <% if @pagination && %w[failing scheduled].include?(params[:status]) %>
74
75
  <div class="row">
75
76
  <%= button_to 'Run All', reschedule_all_jobs_path(status: params[:status]), class: 'btn-danger', method: :post, onclick: "return confirm('Are you sure you wish to reschedule all jobs?')" %>
76
77
  <%= button_to 'Delete All', destroy_all_jobs_path(status: params[:status]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete all jobs?')" %>
77
78
  </div>
78
- <% else %>
79
- <div class="row">
80
- <p>No jobs found</p>
81
- </div>
82
79
  <% end %>
@@ -0,0 +1,29 @@
1
+ <div class="metrics">
2
+ <h1>Queue Metrics</h1>
3
+ <table cellspacing="0">
4
+ <thead>
5
+ <tr>
6
+ <th>Queue</th>
7
+ <th>Scheduled</th>
8
+ <th>Failing</th>
9
+ <th>Running</th>
10
+ <th>Finished</th>
11
+ <th>Expired</th>
12
+ <th>Latency</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% @queue_metrics.each do |queue, values| %>
17
+ <tr>
18
+ <td><%= queue %></td>
19
+ <td><%= link_to values[:scheduled], jobs_path(status: 'scheduled', queue_name: queue) %></td>
20
+ <td><%= link_to values[:failing], jobs_path(status: 'failing', queue_name: queue) %></td>
21
+ <td><%= link_to values[:running], jobs_path(status: 'running', queue_name: queue) %></td>
22
+ <td><%= link_to values[:finished], jobs_path(status: 'finished', queue_name: queue) %></td>
23
+ <td><%= link_to values[:expired], jobs_path(status: 'expired', queue_name: queue) %></td>
24
+ <td></td>
25
+ </tr>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
29
+ </div>
@@ -58,3 +58,29 @@
58
58
  <% end %>
59
59
  </div>
60
60
  </div>
61
+ <div class="lockers">
62
+ <h1>Processes</h1>
63
+ <table cellspacing="0">
64
+ <thead>
65
+ <tr>
66
+ <th>ID</th>
67
+ <th>Worker count</th>
68
+ <th>Worker priorities</th>
69
+ <th>Listening</th>
70
+ </tr>
71
+ </thead>
72
+ <tbody>
73
+ <% @lockers.each.with_index do |locker, index| %>
74
+ <tr>
75
+ <td class="locker-process">
76
+ <span>Process <%= index + 1 %></span>
77
+ <span><strong>Queues:</strong> <%= locker[:queues][1..-2].split(',').join(', ') %></span>
78
+ </td>
79
+ <td><%= locker[:worker_count] %></td>
80
+ <td><%= locker[:worker_priorities] %></td>
81
+ <td><%= locker[:listening] %></td>
82
+ </tr>
83
+ <% end %>
84
+ </tbody>
85
+ </table>
86
+ </div>
data/config/routes.rb CHANGED
@@ -5,6 +5,7 @@ Que::View::Engine.routes.draw do
5
5
  post :reschedule_all, on: :collection
6
6
  delete :destroy_all, on: :collection
7
7
  end
8
+ resources :queue_metrics, only: %i[index]
8
9
 
9
10
  root 'welcome#index'
10
11
  end
data/lib/que/view/dsl.rb CHANGED
@@ -8,6 +8,17 @@ module Que
8
8
  execute(fetch_dashboard_stats_sql)
9
9
  end
10
10
 
11
+ def fetch_que_lockers
12
+ execute(fetch_que_lockers_sql)
13
+ end
14
+
15
+ def fetch_queue_metrics
16
+ execute(fetch_queue_metrics_sql).each_with_object({}) { |element, acc|
17
+ acc[element[:queue_name].to_sym] ||= basis_queue_stats
18
+ acc[element[:queue_name].to_sym][element[:status].to_sym] = element[:count_all]
19
+ }
20
+ end
21
+
11
22
  def fetch_queue_names
12
23
  execute(fetch_queue_names_sql).map { |queues_data|
13
24
  ["#{queues_data[:queue_name]} (#{queues_data[:count_all]})", queues_data[:queue_name]]
@@ -20,7 +31,7 @@ module Que
20
31
  }
21
32
  end
22
33
 
23
- def fetch_running_jobs(...)
34
+ def fetch_running_jobs
24
35
  Que.job_states
25
36
  end
26
37
 
@@ -70,6 +81,10 @@ module Que
70
81
 
71
82
  private
72
83
 
84
+ def basis_queue_stats
85
+ { scheduled: 0, failing: 0, running: 0, finished: 0, expired: 0 }
86
+ end
87
+
73
88
  # rubocop: disable Metrics/MethodLength
74
89
  def fetch_dashboard_stats_sql
75
90
  <<-SQL.squish
@@ -88,6 +103,40 @@ module Que
88
103
  SQL
89
104
  end
90
105
 
106
+ def fetch_que_lockers_sql
107
+ <<-SQL.squish
108
+ SELECT *
109
+ FROM que_lockers
110
+ SQL
111
+ end
112
+
113
+ def fetch_queue_metrics_sql
114
+ <<-SQL.squish
115
+ SELECT COUNT(*) AS count_all, queue AS queue_name,
116
+ CASE
117
+ WHEN expired_at IS NOT NULL THEN 'expired'
118
+ WHEN finished_at IS NOT NULL THEN 'finished'
119
+ WHEN locks.job_id IS NULL AND error_count > 0 THEN 'failing'
120
+ WHEN locks.job_id IS NULL AND error_count = 0 THEN 'scheduled'
121
+ ELSE 'running'
122
+ END status
123
+ FROM que_jobs
124
+ LEFT JOIN (
125
+ SELECT (classid::bigint << 32) + objid::bigint AS job_id
126
+ FROM pg_locks
127
+ WHERE locktype = 'advisory'
128
+ ) locks ON (que_jobs.id=locks.job_id)
129
+ GROUP BY queue,
130
+ CASE
131
+ WHEN expired_at IS NOT NULL THEN 'expired'
132
+ WHEN finished_at IS NOT NULL THEN 'finished'
133
+ WHEN locks.job_id IS NULL AND error_count > 0 THEN 'failing'
134
+ WHEN locks.job_id IS NULL AND error_count = 0 THEN 'scheduled'
135
+ ELSE 'running'
136
+ END
137
+ SQL
138
+ end
139
+
91
140
  def fetch_queue_names_sql
92
141
  <<-SQL.squish
93
142
  SELECT COUNT(*) AS count_all, queue AS queue_name
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Que
4
4
  module View
5
- VERSION = '0.3.1'
5
+ VERSION = '0.3.2'
6
6
  end
7
7
  end
data/lib/que/view.rb CHANGED
@@ -34,7 +34,7 @@ module Que
34
34
  end
35
35
 
36
36
  # Public: All the methods delegated to instance. These should match the interface of Que::View::DSL.
37
- def_delegators :instance, :fetch_dashboard_stats,
37
+ def_delegators :instance, :fetch_dashboard_stats, :fetch_que_lockers, :fetch_queue_metrics,
38
38
  :fetch_queue_names, :fetch_job_names,
39
39
  :fetch_running_jobs, :fetch_failing_jobs, :fetch_scheduled_jobs, :fetch_finished_jobs,
40
40
  :fetch_expired_jobs, :fetch_job,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: que-view
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bogdanov Anton
@@ -164,11 +164,13 @@ files:
164
164
  - app/assets/stylesheets/que/view/application.css
165
165
  - app/controllers/que/view/application_controller.rb
166
166
  - app/controllers/que/view/jobs_controller.rb
167
+ - app/controllers/que/view/queue_metrics_controller.rb
167
168
  - app/controllers/que/view/welcome_controller.rb
168
169
  - app/helpers/que/view/application_helper.rb
169
170
  - app/views/layouts/que/view/application.html.erb
170
171
  - app/views/que/view/jobs/index.html.erb
171
172
  - app/views/que/view/jobs/show.html.erb
173
+ - app/views/que/view/queue_metrics/index.html.erb
172
174
  - app/views/que/view/welcome/index.html.erb
173
175
  - config/routes.rb
174
176
  - lib/que/view.rb