que-view 0.2.3 → 0.3.1

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: 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: