que-view 0.2.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1ced9639e99e58168c4b6e6a43c95f25e7a8dc0e44da579cc3e5bff4260afac
4
- data.tar.gz: 035f401a2e5afe417100ecb908ca54e99d467a7af3822a698af734e27bfbee1c
3
+ metadata.gz: d07972f8adf809f3f10f495824376c64724e1526e7afd6d85a05da93632741e0
4
+ data.tar.gz: 95d6ce6e4d1340dcb550a1ae4b85560f19a18de0b9c0dec1eea5ce5a44726ddf
5
5
  SHA512:
6
- metadata.gz: 38a0e562ee5553257cdddffceb963b87b659cc57fd04fc8c78c0a1b6e6c7863d4c86287f0fc03e6df30bbd8823c758b0b78607c56fd39a04fb345649aad72aee
7
- data.tar.gz: 7466ab6634be76c84a3587eb800f82fbe7cb355eba7914cae29cd4bcf66054ff519b14d11d3a9be3c5927eb9011486e0d78d287ff7b7ada6ca758a5d77943dc6
6
+ metadata.gz: 9553edb70775fc2fa268ad2ec3e14d3ab51c3858b54363efbb0d6b8aae85f00d450d99e925cd576dc7117f1ff784bbbe664d763305123598acc35bda57e38e8b
7
+ data.tar.gz: ce8bc2cbfef7844814f60125bbad375bf676bc2ef14180d5a2bdc2b4a9fd5cc3ac1920f5b5f40b540a2e600ee4107c247c3b89f90d3e7662725de1a6539c2461
data/README.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # Que::View
2
2
  Rails engine inspired by [Que::Web](https://github.com/statianzo/que-web) for [Que](https://github.com/que-rb/que) job queue.
3
3
  SQL queries came from Que::Web, some styling from there too.
4
- Benefits for using this one: independent from Sinatra (que-web based on Sinatra)
4
+
5
+ Benefits for using this one:
6
+ - no Sinatra (que-web based on Sinatra),
7
+ - no Foundation for styles,
8
+ - no jQuery,
9
+ - Que::Web was last updated 2 years ago.
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">
13
+
5
14
 
6
15
  ## Installation
7
16
 
@@ -44,13 +53,5 @@ Add this line to assets/config/manifest.js
44
53
  //= link que/view/application.css
45
54
  ```
46
55
 
47
- ## TODO
48
-
49
- - [X] rescheduling jobs
50
- - [X] deleting jobs
51
- - [ ] better styles for UI
52
- - [ ] rendering running jobs
53
- - [ ] tests
54
-
55
56
  ## License
56
57
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -20,67 +20,64 @@
20
20
  font-family: 'Source Sans Pro';
21
21
  }
22
22
 
23
- .row {
24
- display: flex;
25
- flex-direction: row;
26
- margin: 0 auto;
27
- max-width: 75rem;
28
- width: 100%;
29
- }
30
-
31
- .row .column {
32
- flex: 1;
33
- padding: 1rem;
34
- }
35
-
36
- .row table {
37
- flex: 1;
38
- margin-bottom: 1rem;
39
- }
40
-
41
- table th, table td {
42
- padding: .25rem .75rem;
43
- border-bottom: 1px solid #BBB;
23
+ html, body {
24
+ margin: 0;
25
+ padding: 0;
44
26
  }
45
27
 
46
- table th {
47
- text-align: left;
28
+ body {
29
+ background: #fafafa;
48
30
  }
49
31
 
50
- table tbody tr:nth-of-type(2n), table tbody tr:hover {
51
- background: #DDD;
32
+ .row {
33
+ display: flex;
52
34
  }
53
35
 
54
- table tbody tr:nth-of-type(2n):hover {
55
- background: #CCC;
36
+ .content {
37
+ padding: 1rem;
56
38
  }
57
39
 
58
- table p {
59
- margin: 0;
40
+ .actions {
41
+ display: flex;
60
42
  }
61
43
 
62
- .actions {
44
+ .dashboard-row {
63
45
  display: flex;
46
+ margin: 0 auto;
47
+ width: 100%;
48
+ border: 1px solid #a8a29e;
49
+ border-radius: .25rem;
50
+ overflow: hidden;
64
51
  }
65
52
 
66
- .dashboard-stat {
53
+ .dashboard-row .dashboard-stat {
67
54
  text-align: center;
68
55
  display: table;
69
56
  width: 100%;
57
+ background: #fff;
58
+ border-right: 1px solid #a8a29e;
59
+ }
60
+
61
+ .dashboard-row .dashboard-stat:hover {
62
+ background: #f5f5f4;
70
63
  }
71
64
 
72
- .dashboard-stat a {
65
+ .dashboard-row .dashboard-stat:nth-last-of-type(1) {
66
+ border: none;
67
+ }
68
+
69
+ .dashboard-row .dashboard-stat a {
73
70
  text-decoration: none;
74
71
  }
75
72
 
76
- .dashboard-stat .cell {
73
+ .dashboard-row .dashboard-stat .cell {
77
74
  display: flex;
78
75
  flex-direction: column;
79
76
  justify-content: center;
80
- height: 200px;
77
+ height: 150px;
81
78
  }
82
79
 
83
- .dashboard-stat h2 {
80
+ .dashboard-row .dashboard-stat h2 {
84
81
  color: #222;
85
82
  font-size: 1rem;
86
83
  font-weight: normal;
@@ -88,24 +85,23 @@ table p {
88
85
  margin: 0;
89
86
  }
90
87
 
91
- .dashboard-stat.running {
92
- background: #CFD0C1;
93
- }
94
-
95
- .dashboard-stat.scheduled {
96
- background: #828E8C;
97
- }
98
-
99
- .dashboard-stat.failing {
100
- background: #E8866C;
101
- }
102
-
103
88
  .dashboard-value {
104
- font-size: 5rem;
105
- line-height: 5rem;
89
+ font-size: 2rem;
90
+ line-height: 2rem;
106
91
  color: black;
107
92
  }
108
93
 
94
+ .btn-primary {
95
+ cursor: pointer;
96
+ border: none;
97
+ border-radius: .25rem;
98
+ background: #fde68a;
99
+ border: 1px solid #fcd34d;
100
+ border-radius: .25rem;
101
+ padding: .25rem .5rem;
102
+ margin-right: .5rem;
103
+ }
104
+
109
105
  .btn-danger {
110
106
  cursor: pointer;
111
107
  border: none;
@@ -114,3 +110,124 @@ table p {
114
110
  padding: .25rem .5rem;
115
111
  margin-right: .5rem;
116
112
  }
113
+
114
+ .pagination {
115
+ margin: 1rem auto;
116
+ }
117
+
118
+ .pagination .pagination-link {
119
+ margin: 0 .5rem 0 0;
120
+ padding: .25rem .5rem;
121
+ background: #fde68a;
122
+ border: 1px solid #fcd34d;
123
+ border-radius: .25rem;
124
+ text-decoration: none;
125
+ }
126
+
127
+ .pagination .pagination-link.disabled {
128
+ background: #fef3c7;
129
+ }
130
+
131
+ .pagination .total-pages {
132
+ margin: 0 .5rem 0 0;
133
+ padding: .25rem .5rem;
134
+ }
135
+
136
+ .navigation {
137
+ display: flex;
138
+ padding: 0 1rem;
139
+ background: #fff;
140
+ border-bottom: 1px solid #e7e5e4;
141
+ color: #000;
142
+ }
143
+
144
+ .navigation h1 {
145
+ margin: 0 2rem 0 0;
146
+ display: flex;
147
+ align-items: center;
148
+ }
149
+
150
+ .navigation h1 a {
151
+ text-decoration: none;
152
+ color: #000;
153
+ }
154
+
155
+ .navigation .navigation-section {
156
+ flex: 1;
157
+ display: flex;
158
+ justify-content: space-between;
159
+ align-items: center;
160
+ }
161
+
162
+ .navigation .navigation-section ul {
163
+ margin: 0;
164
+ padding: 0;
165
+ list-style: none;
166
+ display: flex;
167
+ }
168
+
169
+ .navigation .navigation-section ul.version li {
170
+ margin-left: 1rem;
171
+ }
172
+
173
+ .navigation .navigation-section ul li a {
174
+ display: inline-block;
175
+ padding: 1rem;
176
+ text-decoration: none;
177
+ color: #000;
178
+ }
179
+
180
+ .navigation .navigation-section ul li.active {
181
+ background: #e7e5e4;
182
+ }
183
+
184
+ .search-form .form-select {
185
+ margin-right: 1rem;
186
+ padding: .5rem 1rem;
187
+ border: 1px solid #e7e5e4;
188
+ border-radius: .25rem;
189
+ background: #fff;
190
+ }
191
+
192
+ table {
193
+ width: 100%;
194
+ margin-bottom: 1rem;
195
+ background: #fff;
196
+ border: 1px solid #e7e5e4;
197
+ border-bottom: none;
198
+ border-radius: .25rem;
199
+ }
200
+
201
+ table th, table td {
202
+ padding: .5rem .75rem;
203
+ border-bottom: 1px solid #e7e5e4;
204
+ }
205
+
206
+ table th {
207
+ text-align: left;
208
+ font-weight: normal;
209
+ text-transform: uppercase;
210
+ }
211
+
212
+ table tbody tr:nth-of-type(2n), table tbody tr:hover {
213
+ background: #EEE;
214
+ }
215
+
216
+ table tbody tr:nth-of-type(2n):hover {
217
+ background: #DDD;
218
+ }
219
+
220
+ table p {
221
+ margin: 0;
222
+ }
223
+
224
+ @media screen and (max-width: 768px) {
225
+ .dashboard-row {
226
+ flex-direction: column;
227
+ }
228
+
229
+ .dashboard-row .dashboard-stat {
230
+ border-right: none;
231
+ border-bottom: 1px solid #a8a29e;
232
+ }
233
+ }
@@ -5,11 +5,13 @@ module Que
5
5
  class JobsController < Que::View::ApplicationController
6
6
  PER_PAGE = 20
7
7
 
8
+ before_action :find_queue_names, only: %i[index]
9
+ before_action :find_job_names, only: %i[index]
8
10
  before_action :find_job, only: %i[show]
9
11
 
10
12
  def index
11
- @jobs = find_jobs(params[:status])
12
- @jobs_amount = find_jobs_amount(params[:status])
13
+ @jobs = find_jobs(index_params)
14
+ paginate
13
15
  end
14
16
 
15
17
  def show; end
@@ -48,6 +50,18 @@ module Que
48
50
 
49
51
  private
50
52
 
53
+ def find_queue_names
54
+ @queue_names = [
55
+ ['All queues', nil]
56
+ ] + ::Que::View.fetch_queue_names
57
+ end
58
+
59
+ def find_job_names
60
+ @job_names = [
61
+ ['All jobs', nil]
62
+ ] + ::Que::View.fetch_job_names(params[:queue_name])
63
+ end
64
+
51
65
  def find_job
52
66
  @job = ::Que::View.fetch_job(params[:id])[0]
53
67
  return if @job
@@ -55,16 +69,32 @@ module Que
55
69
  redirect_to root_path, notice: 'Job is not found'
56
70
  end
57
71
 
58
- def find_jobs(status)
59
- case status&.to_sym
60
- when :failing then ::Que::View.fetch_failing_jobs(PER_PAGE, offset, search)
61
- when :scheduled then ::Que::View.fetch_scheduled_jobs(PER_PAGE, offset, search)
72
+ def paginate
73
+ return if %w[failing scheduled].exclude?(params[:status])
74
+ return unless @jobs.any?
75
+
76
+ @pagination = Que::View::Pagination.new(
77
+ params: {
78
+ page: page,
79
+ per_page: params[:per_page] || PER_PAGE,
80
+ count: find_jobs_total_amount(params[:status])
81
+ }
82
+ )
83
+ end
84
+
85
+ def find_jobs(params)
86
+ case params[:status]&.to_sym
87
+ when :running then ::Que::View.fetch_running_jobs(params)
88
+ when :failing then ::Que::View.fetch_failing_jobs(PER_PAGE, offset, params)
89
+ when :scheduled then ::Que::View.fetch_scheduled_jobs(PER_PAGE, offset, params)
90
+ when :finished then ::Que::View.fetch_finished_jobs(PER_PAGE, offset, params)
91
+ when :expired then ::Que::View.fetch_expired_jobs(PER_PAGE, offset, params)
62
92
  else []
63
93
  end
64
94
  end
65
95
 
66
- def find_jobs_amount(status)
67
- ::Que::View.fetch_dashboard_stats(search)[0][status&.to_sym]
96
+ def find_jobs_total_amount(status)
97
+ ::Que::View.fetch_dashboard_stats[0][status&.to_sym]
68
98
  end
69
99
 
70
100
  def reschedule_all_jobs(status, time)
@@ -83,25 +113,16 @@ module Que
83
113
  end
84
114
  end
85
115
 
86
- def search
87
- return '%' unless search_param
88
-
89
- "%#{search_param}%"
90
- end
91
-
92
- def search_param
93
- sanitised = (params[:search] || '').gsub(/[^0-9a-z:]/i, '')
94
- return if sanitised.empty?
95
-
96
- sanitised
116
+ def offset
117
+ (page - 1) * PER_PAGE
97
118
  end
98
119
 
99
120
  def page
100
121
  (params[:page] || 1).to_i
101
122
  end
102
123
 
103
- def offset
104
- (page - 1) * PER_PAGE
124
+ def index_params
125
+ params.permit(:status, :queue_name, :job_name).to_h.symbolize_keys
105
126
  end
106
127
  end
107
128
  end
@@ -4,7 +4,7 @@ module Que
4
4
  module View
5
5
  class WelcomeController < Que::View::ApplicationController
6
6
  def index
7
- @dashboard_stats = ::Que::View.fetch_dashboard_stats('%')[0]
7
+ @dashboard_stats = ::Que::View.fetch_dashboard_stats[0]
8
8
  end
9
9
  end
10
10
  end
@@ -7,6 +7,35 @@
7
7
  <%= stylesheet_link_tag 'que/view/application', media: 'all' %>
8
8
  </head>
9
9
  <body>
10
- <%= yield %>
10
+ <nav class="navigation" role="navigation">
11
+ <h1><%= link_to 'Que View', root_path %></h1>
12
+ <section class="navigation-section">
13
+ <ul>
14
+ <li class="<%= 'active' if params[:status] == 'scheduled' %>">
15
+ <%= link_to 'Scheduled', jobs_path(status: 'scheduled') %>
16
+ </li>
17
+ <li class="<%= 'active' if params[:status] == 'failing' %>">
18
+ <%= link_to 'Failing', jobs_path(status: 'failing') %>
19
+ </li>
20
+ <li class="<%= 'active' if params[:status] == 'running' %>">
21
+ <%= link_to 'Running', jobs_path(status: 'running') %>
22
+ </li>
23
+ <li class="<%= 'active' if params[:status] == 'finished' %>">
24
+ <%= link_to 'Finished', jobs_path(status: 'finished') %>
25
+ </li>
26
+ <li class="<%= 'active' if params[:status] == 'expired' %>">
27
+ <%= link_to 'Expired', jobs_path(status: 'expired') %>
28
+ </li>
29
+ </ul>
30
+ <ul class="version">
31
+ <li>Que <%= Que::VERSION %></li>
32
+ <li>Que View <%= Que::View::VERSION %></li>
33
+ <li><%= Time.now.utc.strftime("%Y-%m-%d %H:%M:%S") %></li>
34
+ </ul>
35
+ </section>
36
+ </nav>
37
+ <section class="content">
38
+ <%= yield %>
39
+ </section>
11
40
  </body>
12
41
  </html>
@@ -1,55 +1,82 @@
1
- <div class="row">
2
- <% if @jobs.size.positive? %>
3
- <table cellspacing="0">
4
- <thead>
1
+ <%= form_with url: jobs_path, method: :get, class: 'search-form' do |form| %>
2
+ <%= form.hidden_field :status, value: params[:status] %>
3
+ <div class="row">
4
+ <%= form.select :queue_name, options_for_select(@queue_names, params[:queue_name]), {}, class: 'form-select' %>
5
+ <%= form.select :job_name, options_for_select(@job_names, params[:job_name]), {}, class: 'form-select' %>
6
+ <%= form.submit 'Search', class: 'btn-primary' %>
7
+ </div>
8
+ <% 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>
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>
40
+ <% end %>
41
+ <% if %w[failing scheduled].include?(params[:status]) %>
42
+ <th>Actions</th>
43
+ <% end %>
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ <% @jobs.each do |job| %>
5
48
  <tr>
6
- <th>ID</th>
7
- <th>Run at</th>
8
- <th>Job</th>
9
- <th>Arguments</th>
10
- <th>Queue</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>
11
58
  <% if params[:status] == 'failing' %>
12
- <th>Failures</th>
13
- <th>Error</th>
59
+ <td><%= job[:error_count] %></td>
60
+ <td><%= format_error(job) %></td>
14
61
  <% end %>
15
62
  <% if %w[failing scheduled].include?(params[:status]) %>
16
- <th></th>
17
- <% end %>
18
- </tr>
19
- </thead>
20
- <tbody>
21
- <% @jobs.each do |job| %>
22
- <tr>
23
- <td><%= link_to job[:id], job_path(job[:id]) %></td>
24
- <td><%= job[:run_at].utc %></td>
25
- <td><%= humanized_job_class(job) %></td>
26
- <td><%= job[:queue] %></td>
27
63
  <td>
28
- <% job.dig(:args, 0, :arguments).each do |argument| %>
29
- <p><%= argument %></p>
30
- <% end %>
31
- </td>
32
- <% if params[:status] == 'failing' %>
33
- <td><%= job[:error_count] %></td>
34
- <td><%= format_error(job) %></td>
35
- <% end %>
36
- <% if %w[failing scheduled].include?(params[:status]) %>
37
- <td class="actions">
64
+ <div class="actions">
38
65
  <%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
39
66
  <%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
40
- </td>
41
- <% end %>
42
- </tr>
43
- <% end %>
44
- </tbody>
45
- </table>
46
- <% else %>
47
- <p>No jobs found</p>
48
- <% end %>
49
- </div>
50
- <% if %w[failing scheduled].include?(params[:status]) && @jobs_amount.positive? %>
67
+ </div>
68
+ </td>
69
+ <% end %>
70
+ </tr>
71
+ <% end %>
72
+ </tbody>
73
+ </table>
51
74
  <div class="row">
52
75
  <%= 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?')" %>
53
76
  <%= 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?')" %>
54
77
  </div>
78
+ <% else %>
79
+ <div class="row">
80
+ <p>No jobs found</p>
81
+ </div>
55
82
  <% end %>
@@ -20,7 +20,7 @@
20
20
  </tr>
21
21
  <tr>
22
22
  <th>Run at</th>
23
- <td><%= @job[:run_at].utc %></td>
23
+ <td><%= @job[:run_at].strftime("%Y-%m-%d %H:%M:%S") %></td>
24
24
  </tr>
25
25
  <tr>
26
26
  <th>Failures</th>
@@ -1,38 +1,60 @@
1
- <div class="row">
2
- <div class="column">
3
- <div class="dashboard-stat running">
4
- <%= link_to jobs_path(status: 'running') do %>
5
- <div class="cell">
6
- <h2>Running</h2>
7
- <span class="dashboard-value">
8
- <%= @dashboard_stats[:running] %>
9
- </span>
10
- </div>
11
- <% end %>
1
+ <div class="dashboard-row">
2
+ <div class="dashboard-stat">
3
+ <div class="cell">
4
+ <h2>Total</h2>
5
+ <span class="dashboard-value">
6
+ <%= @dashboard_stats[:total] %>
7
+ </span>
12
8
  </div>
13
9
  </div>
14
- <div class="column">
15
- <div class="dashboard-stat scheduled">
16
- <%= link_to jobs_path(status: 'scheduled') do %>
17
- <div class="cell">
18
- <h2>Scheduled</h2>
19
- <span class="dashboard-value">
20
- <%= @dashboard_stats[:scheduled] %>
21
- </span>
22
- </div>
23
- <% end %>
24
- </div>
10
+ <div class="dashboard-stat">
11
+ <%= link_to jobs_path(status: 'scheduled') do %>
12
+ <div class="cell">
13
+ <h2>Scheduled</h2>
14
+ <span class="dashboard-value">
15
+ <%= @dashboard_stats[:scheduled] %>
16
+ </span>
17
+ </div>
18
+ <% end %>
25
19
  </div>
26
- <div class="column">
27
- <div class="dashboard-stat failing">
28
- <%= link_to jobs_path(status: 'failing') do %>
29
- <div class="cell">
30
- <h2>Failing</h2>
31
- <span class="dashboard-value">
32
- <%= @dashboard_stats[:failing] %>
33
- </span>
34
- </div>
35
- <% end %>
36
- </div>
20
+ <div class="dashboard-stat">
21
+ <%= link_to jobs_path(status: 'failing') do %>
22
+ <div class="cell">
23
+ <h2>Failing</h2>
24
+ <span class="dashboard-value">
25
+ <%= @dashboard_stats[:failing] %>
26
+ </span>
27
+ </div>
28
+ <% end %>
29
+ </div>
30
+ <div class="dashboard-stat">
31
+ <%= link_to jobs_path(status: 'running') do %>
32
+ <div class="cell">
33
+ <h2>Running</h2>
34
+ <span class="dashboard-value">
35
+ <%= @dashboard_stats[:running] %>
36
+ </span>
37
+ </div>
38
+ <% end %>
39
+ </div>
40
+ <div class="dashboard-stat">
41
+ <%= link_to jobs_path(status: 'finished') do %>
42
+ <div class="cell">
43
+ <h2>Finished</h2>
44
+ <span class="dashboard-value">
45
+ <%= @dashboard_stats[:finished] %>
46
+ </span>
47
+ </div>
48
+ <% end %>
49
+ </div>
50
+ <div class="dashboard-stat">
51
+ <%= link_to jobs_path(status: 'expired') do %>
52
+ <div class="cell">
53
+ <h2>Expired</h2>
54
+ <span class="dashboard-value">
55
+ <%= @dashboard_stats[:expired] %>
56
+ </span>
57
+ </div>
58
+ <% end %>
37
59
  </div>
38
60
  </div>
data/lib/que/view/dsl.rb CHANGED
@@ -4,8 +4,24 @@ module Que
4
4
  module View
5
5
  # rubocop: disable Metrics/ClassLength
6
6
  class DSL
7
- def fetch_dashboard_stats(...)
8
- execute(fetch_dashboard_stats_sql(...))
7
+ def fetch_dashboard_stats
8
+ execute(fetch_dashboard_stats_sql)
9
+ end
10
+
11
+ def fetch_queue_names
12
+ execute(fetch_queue_names_sql).map { |queues_data|
13
+ ["#{queues_data[:queue_name]} (#{queues_data[:count_all]})", queues_data[:queue_name]]
14
+ }
15
+ end
16
+
17
+ def fetch_job_names(...)
18
+ execute(fetch_job_names_sql(...)).map { |jobs_data|
19
+ ["#{jobs_data[:job_name]} (#{jobs_data[:count_all]})", jobs_data[:job_name]]
20
+ }
21
+ end
22
+
23
+ def fetch_running_jobs(...)
24
+ Que.job_states
9
25
  end
10
26
 
11
27
  def fetch_failing_jobs(...)
@@ -16,6 +32,14 @@ module Que
16
32
  execute(fetch_scheduled_jobs_sql(...))
17
33
  end
18
34
 
35
+ def fetch_finished_jobs(...)
36
+ execute(fetch_finished_jobs_sql(...))
37
+ end
38
+
39
+ def fetch_expired_jobs(...)
40
+ execute(fetch_expired_jobs_sql(...))
41
+ end
42
+
19
43
  def fetch_job(...)
20
44
  execute(fetch_job_sql(...))
21
45
  end
@@ -47,46 +71,82 @@ module Que
47
71
  private
48
72
 
49
73
  # rubocop: disable Metrics/MethodLength
50
- def fetch_dashboard_stats_sql(search)
74
+ def fetch_dashboard_stats_sql
51
75
  <<-SQL.squish
52
- SELECT count(*) AS total,
53
- count(locks.job_id) AS running,
76
+ SELECT count(*) AS total,
77
+ count(locks.job_id) AS running,
54
78
  coalesce(sum((error_count > 0 AND locks.job_id IS NULL)::int), 0) AS failing,
55
- coalesce(sum((error_count = 0 AND locks.job_id IS NULL)::int), 0) AS scheduled
79
+ coalesce(sum((error_count = 0 AND locks.job_id IS NULL)::int), 0) AS scheduled,
80
+ coalesce(sum((finished_at IS NOT NULL)::int), 0) AS finished,
81
+ coalesce(sum((expired_at IS NOT NULL)::int), 0) AS expired
56
82
  FROM que_jobs
57
83
  LEFT JOIN (
58
84
  SELECT (classid::bigint << 32) + objid::bigint AS job_id
59
85
  FROM pg_locks
60
86
  WHERE locktype = 'advisory'
61
87
  ) locks ON (que_jobs.id=locks.job_id)
62
- WHERE
63
- job_class ILIKE ('#{search}')
64
- OR que_jobs.args #>> '{0, job_class}' ILIKE ('#{search}')
65
88
  SQL
66
89
  end
67
90
 
68
- def fetch_failing_jobs_sql(per_page, offset, search)
91
+ def fetch_queue_names_sql
69
92
  <<-SQL.squish
70
- SELECT que_jobs.*
93
+ SELECT COUNT(*) AS count_all, queue AS queue_name
71
94
  FROM que_jobs
72
- LEFT JOIN (
73
- SELECT (classid::bigint << 32) + objid::bigint AS job_id
74
- FROM pg_locks
75
- WHERE locktype = 'advisory'
76
- ) locks ON (que_jobs.id=locks.job_id)
95
+ GROUP BY queue
96
+ SQL
97
+ end
98
+
99
+ def fetch_job_names_sql(queue_name)
100
+ <<-SQL.squish
101
+ SELECT COUNT(*) AS count_all, args #>> '{0, job_class}' AS job_name
102
+ FROM que_jobs
103
+ #{queue_name.present? ? "WHERE queue = '#{queue_name}'" : ""}
104
+ GROUP BY args #>> '{0, job_class}'
105
+ SQL
106
+ end
107
+
108
+ def fetch_failing_jobs_sql(per_page, offset, params)
109
+ where_condition = <<-SQL.squish
77
110
  WHERE locks.job_id IS NULL
78
111
  AND error_count > 0
79
- AND (
80
- job_class ILIKE ('#{search}')
81
- OR que_jobs.args #>> '{0, job_class}' ILIKE ('#{search}')
82
- )
83
- ORDER BY run_at, id
84
- LIMIT #{per_page}::int
85
- OFFSET #{offset}::int
112
+ #{search_condition(params)}
113
+ SQL
114
+ fetch_jobs_sql(per_page, offset, where_condition)
115
+ end
116
+
117
+ def fetch_scheduled_jobs_sql(per_page, offset, params)
118
+ where_condition = <<-SQL.squish
119
+ WHERE locks.job_id IS NULL
120
+ AND error_count = 0
121
+ #{search_condition(params)}
86
122
  SQL
123
+ fetch_jobs_sql(per_page, offset, where_condition)
87
124
  end
88
125
 
89
- def fetch_scheduled_jobs_sql(per_page, offset, search)
126
+ def fetch_finished_jobs_sql(per_page, offset, params)
127
+ where_condition = <<-SQL.squish
128
+ WHERE finished_at IS NOT NULL
129
+ #{search_condition(params)}
130
+ SQL
131
+ fetch_jobs_sql(per_page, offset, where_condition)
132
+ end
133
+
134
+ def fetch_expired_jobs_sql(per_page, offset, params)
135
+ where_condition = <<-SQL.squish
136
+ WHERE expired_at IS NOT NULL
137
+ #{search_condition(params)}
138
+ SQL
139
+ fetch_jobs_sql(per_page, offset, where_condition)
140
+ end
141
+
142
+ def search_condition(params)
143
+ result = ''
144
+ result += "AND queue = '#{params[:queue_name]}'" if params[:queue_name].present?
145
+ result += "AND args #>> '{0, job_class}' = ('#{params[:job_name]}')" if params[:job_name].present?
146
+ result
147
+ end
148
+
149
+ def fetch_jobs_sql(per_page, offset, where_condition)
90
150
  <<-SQL.squish
91
151
  SELECT que_jobs.*
92
152
  FROM que_jobs
@@ -95,12 +155,7 @@ module Que
95
155
  FROM pg_locks
96
156
  WHERE locktype = 'advisory'
97
157
  ) locks ON (que_jobs.id=locks.job_id)
98
- WHERE locks.job_id IS NULL
99
- AND error_count = 0
100
- AND (
101
- job_class ILIKE ('#{search}')
102
- OR que_jobs.args #>> '{0, job_class}' ILIKE ('#{search}')
103
- )
158
+ #{where_condition}
104
159
  ORDER BY run_at, id
105
160
  LIMIT #{per_page}::int
106
161
  OFFSET #{offset}::int
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Que
4
+ module View
5
+ class Pagination
6
+ attr_reader :page, :count, :per_page
7
+
8
+ def initialize(params: {})
9
+ @page = params[:page]
10
+ @count = params[:count]
11
+ @per_page = params[:per_page]
12
+ end
13
+
14
+ def offset
15
+ return 0 if page == 1
16
+
17
+ per_page * (page.to_i - 1)
18
+ end
19
+
20
+ def next_page
21
+ page + 1 unless last_page?
22
+ end
23
+
24
+ def next_page?
25
+ page < total_pages
26
+ end
27
+
28
+ def previous_page
29
+ page - 1 unless first_page?
30
+ end
31
+
32
+ def previous_page?
33
+ page > 1
34
+ end
35
+
36
+ def last_page?
37
+ page == total_pages
38
+ end
39
+
40
+ def first_page?
41
+ page == 1
42
+ end
43
+
44
+ def total_pages
45
+ (count / per_page.to_f).ceil
46
+ end
47
+ end
48
+ end
49
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Que
4
4
  module View
5
- VERSION = '0.2.3'
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  end
data/lib/que/view.rb CHANGED
@@ -6,13 +6,14 @@ require 'que/view/version'
6
6
  require 'que/view/engine'
7
7
  require 'que/view/configuration'
8
8
  require 'que/view/dsl'
9
+ require 'que/view/pagination'
9
10
 
10
11
  module Que
11
12
  module View
12
13
  extend self
13
14
  extend Forwardable
14
15
 
15
- # Public: Configure emailbutler.
16
+ # Public: Configure que view.
16
17
  #
17
18
  # Que::View.configure do |config|
18
19
  # end
@@ -26,15 +27,17 @@ module Que
26
27
  @configuration ||= Configuration.new
27
28
  end
28
29
 
29
- # Public: Default per thread emailbutler instance if configured.
30
+ # Public: Default per thread que view instance if configured.
30
31
  # Returns Que::View::DSL instance.
31
32
  def instance
32
33
  Thread.current[:que_view_instance] ||= DSL.new
33
34
  end
34
35
 
35
36
  # Public: All the methods delegated to instance. These should match the interface of Que::View::DSL.
36
- def_delegators :instance,
37
- :fetch_dashboard_stats, :fetch_failing_jobs, :fetch_scheduled_jobs, :fetch_job,
37
+ def_delegators :instance, :fetch_dashboard_stats,
38
+ :fetch_queue_names, :fetch_job_names,
39
+ :fetch_running_jobs, :fetch_failing_jobs, :fetch_scheduled_jobs, :fetch_finished_jobs,
40
+ :fetch_expired_jobs, :fetch_job,
38
41
  :delete_failing_jobs, :delete_scheduled_jobs, :delete_job,
39
42
  :reschedule_scheduled_jobs, :reschedule_failing_jobs, :reschedule_job
40
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: que-view
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bogdanov Anton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-01 00:00:00.000000000 Z
11
+ date: 2024-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: que
@@ -175,6 +175,7 @@ files:
175
175
  - lib/que/view/configuration.rb
176
176
  - lib/que/view/dsl.rb
177
177
  - lib/que/view/engine.rb
178
+ - lib/que/view/pagination.rb
178
179
  - lib/que/view/version.rb
179
180
  homepage: https://github.com/kortirso/que-view
180
181
  licenses: