que-view 0.1.0 → 0.2.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: 3a3458af823c9bb66e334138e63455e4c0dc94392d6f09b959276733bcb30ef3
4
- data.tar.gz: c4b4be700ff4456f882488ccdd0adf34af14fa98bd9ab94380cc21a835ac1d49
3
+ metadata.gz: 9576dbcacaced63da0924a3315af1dede11887b6dcfdbca1029356691cd1dd02
4
+ data.tar.gz: abb8d73cfb96eda19ce45fe767edb91e57b9be595242c0a802503d0e16c3dc31
5
5
  SHA512:
6
- metadata.gz: dd7d0ce017b6c4e2becf99b429dd0450aee7b250761ee3d9772d1dd5dc1e9f3bbfaf55508ab0e120ec3a30513e62a0857b5b3c375d2d44835c75f34b16b0c1aa
7
- data.tar.gz: f3aeb0ced2fc4b59bf5e783073505a51f507ea5114a79df6034c9764a8e12637a18f912581399492fef18fb33973c53144493a25dffa86c57a1aaf6a6c2c64d4
6
+ metadata.gz: e44bde7cb0ffd37a3f7ec82dd85639c961a5cf10aab179b75a7f1ea02edc5e432cd1f9655a204f6d16e6d3addeb9d50ffc79df5b8b51d8b01d335433f8d1587a
7
+ data.tar.gz: fe3ada013eff8f6660126c56975e68e663f5c85ab2cd32957c4306e0c419d2285156604262677e02039c289cb22cf0921ca5541781dbb7b2efed1feec186c60e
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Que::View
2
- Short description and motivation.
3
-
4
- ## Usage
5
- How to use my plugin.
2
+ Rails engine inspired by [Que::Web](https://github.com/statianzo/que-web) for [Que](https://github.com/que-rb/que) job queue.
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)
6
5
 
7
6
  ## Installation
7
+
8
8
  Add this line to your application's Gemfile:
9
9
 
10
10
  ```ruby
@@ -17,13 +17,40 @@ And then execute:
17
17
  $ bundle install
18
18
  ```
19
19
 
20
- Or install it yourself as:
21
- ```bash
22
- $ gem install que-view
20
+ ## Configuration
21
+
22
+ You can configure username/password for production web view.
23
+ Add this lines to config/initializers/que_view.rb
24
+
25
+ ```ruby
26
+ Que::View.configure do |config|
27
+ config.ui_username = 'username'
28
+ config.ui_password = 'password'
29
+ config.ui_secured_environments = ['production']
30
+ end
23
31
  ```
24
32
 
25
- ## Contributing
26
- Contribution directions go here.
33
+ ## Usage
34
+
35
+ Add this line to config/routes.rb
36
+
37
+ ```ruby
38
+ mount Que::Web::Engine => '/que_web'
39
+ ```
40
+
41
+ Add this line to assets/config/manifest.js
42
+
43
+ ```js
44
+ //= link que/view/application.css
45
+ ```
46
+
47
+ ## TODO
48
+
49
+ - [X] rescheduling jobs
50
+ - [X] deleting jobs
51
+ - [ ] better styles for UI
52
+ - [ ] rendering running jobs
53
+ - [ ] tests
27
54
 
28
55
  ## License
29
56
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -24,24 +24,45 @@
24
24
  display: flex;
25
25
  flex-direction: row;
26
26
  margin: 0 auto;
27
- max-width: 62.5rem;
27
+ max-width: 75rem;
28
28
  width: 100%;
29
29
  }
30
30
 
31
- .row .column, .row table {
31
+ .row .column {
32
32
  flex: 1;
33
33
  padding: 1rem;
34
34
  }
35
35
 
36
+ .row table {
37
+ flex: 1;
38
+ margin-bottom: 1rem;
39
+ }
40
+
36
41
  table th, table td {
37
42
  padding: .25rem .75rem;
38
- border-bottom: 1px solid #999;
43
+ border-bottom: 1px solid #BBB;
39
44
  }
40
45
 
41
- table tbody tr:nth-of-type(2n) {
46
+ table th {
47
+ text-align: left;
48
+ }
49
+
50
+ table tbody tr:nth-of-type(2n), table tbody tr:hover {
42
51
  background: #DDD;
43
52
  }
44
53
 
54
+ table tbody tr:nth-of-type(2n):hover {
55
+ background: #CCC;
56
+ }
57
+
58
+ table p {
59
+ margin: 0;
60
+ }
61
+
62
+ .actions {
63
+ display: flex;
64
+ }
65
+
45
66
  .dashboard-stat {
46
67
  text-align: center;
47
68
  display: table;
@@ -67,18 +88,14 @@ table tbody tr:nth-of-type(2n) {
67
88
  margin: 0;
68
89
  }
69
90
 
70
- .dashboard-stat.total {
71
- background: #828E8C;
91
+ .dashboard-stat.running {
92
+ background: #CFD0C1;
72
93
  }
73
94
 
74
95
  .dashboard-stat.scheduled {
75
96
  background: #828E8C;
76
97
  }
77
98
 
78
- .dashboard-stat.running {
79
- background: #CFD0C1;
80
- }
81
-
82
99
  .dashboard-stat.failing {
83
100
  background: #E8866C;
84
101
  }
@@ -88,3 +105,12 @@ table tbody tr:nth-of-type(2n) {
88
105
  line-height: 5rem;
89
106
  color: black;
90
107
  }
108
+
109
+ .btn-danger {
110
+ cursor: pointer;
111
+ border: none;
112
+ border-radius: .25rem;
113
+ background: #E8866C;
114
+ padding: .25rem .5rem;
115
+ margin-right: .5rem;
116
+ }
@@ -3,6 +3,20 @@
3
3
  module Que
4
4
  module View
5
5
  class ApplicationController < ActionController::Base
6
+ http_basic_authenticate_with name: ::Que::View.configuration.ui_username,
7
+ password: ::Que::View.configuration.ui_password,
8
+ if: -> { basic_auth_enabled? }
9
+
10
+ private
11
+
12
+ def basic_auth_enabled?
13
+ configuration = ::Que::View.configuration
14
+
15
+ return false if configuration.ui_username.blank?
16
+ return false if configuration.ui_password.blank?
17
+
18
+ configuration.ui_secured_environments.include?(Rails.env)
19
+ end
6
20
  end
7
21
  end
8
22
  end
@@ -5,15 +5,36 @@ module Que
5
5
  class JobsController < Que::View::ApplicationController
6
6
  PER_PAGE = 20
7
7
 
8
+ before_action :find_job, only: %i[show]
9
+
8
10
  def index
9
11
  @jobs = find_jobs(params[:status])
10
12
  @jobs_amount = find_jobs_amount(params[:status])
11
13
  end
12
14
 
15
+ def show; end
16
+
17
+ def update
18
+ updated_rows = ::Que::View.reschedule_job(params[:id], Time.now)
19
+ redirect_to(
20
+ root_path,
21
+ notice: updated_rows.empty? ? 'Job is not rescheduled' : 'Job is rescheduled'
22
+ )
23
+ end
24
+
13
25
  def destroy
26
+ updated_rows = ::Que::View.delete_job(params[:id])
27
+ redirect_to(
28
+ root_path,
29
+ notice: updated_rows.empty? ? 'Job is not deleted' : 'Job is deleted'
30
+ )
31
+ end
32
+
33
+ def reschedule_all
34
+ updated_rows = reschedule_all_jobs(params[:status], Time.now)
14
35
  redirect_to(
15
36
  jobs_path(status: params[:status]),
16
- notice: 'Removing single job is not ready yet'
37
+ notice: updated_rows.empty? ? 'No jobs rescheduled' : "#{updated_rows.count} jobs rescheduled"
17
38
  )
18
39
  end
19
40
 
@@ -27,24 +48,54 @@ module Que
27
48
 
28
49
  private
29
50
 
51
+ def find_job
52
+ @job = ::Que::View.fetch_job(params[:id])[0]
53
+ return if @job
54
+
55
+ redirect_to root_path, notice: 'Job is not found'
56
+ end
57
+
30
58
  def find_jobs(status)
31
59
  case status&.to_sym
32
- when :failing then ::Que::View.failing_jobs(PER_PAGE, offset, '%')
60
+ when :failed then ::Que::View.fetch_failed_jobs(PER_PAGE, offset, search)
61
+ when :scheduled then ::Que::View.fetch_scheduled_jobs(PER_PAGE, offset, search)
33
62
  else []
34
63
  end
35
64
  end
36
65
 
37
66
  def find_jobs_amount(status)
38
- ::Que::View.dashboard_stats('%')[0][status&.to_sym]
67
+ ::Que::View.fetch_dashboard_stats(search)[0][status&.to_sym]
68
+ end
69
+
70
+ def reschedule_all_jobs(status, time)
71
+ case status&.to_sym
72
+ when :failed then ::Que::View.reschedule_failed_jobs(time)
73
+ when :scheduled then ::Que::View.reschedule_scheduled_jobs(time)
74
+ else 0
75
+ end
39
76
  end
40
77
 
41
78
  def destroy_all_jobs(status)
42
79
  case status&.to_sym
43
- when :failing then ::Que::View.delete_all_failing_jobs
80
+ when :failed then ::Que::View.delete_failed_jobs
81
+ when :scheduled then ::Que::View.delete_scheduled_jobs
44
82
  else 0
45
83
  end
46
84
  end
47
85
 
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
97
+ end
98
+
48
99
  def page
49
100
  (params[:page] || 1).to_i
50
101
  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.dashboard_stats('%')[0]
7
+ @dashboard_stats = ::Que::View.fetch_dashboard_stats('%')[0]
8
8
  end
9
9
  end
10
10
  end
@@ -3,8 +3,6 @@
3
3
  module Que
4
4
  module View
5
5
  module ApplicationHelper
6
- include ::Pagy::Frontend
7
-
8
6
  def humanized_job_class(job)
9
7
  case job[:job_class]
10
8
  when 'ActiveJob::QueueAdapters::QueAdapter::JobWrapper' then job.dig(:args, 0, :job_class)
@@ -4,24 +4,41 @@
4
4
  <thead>
5
5
  <tr>
6
6
  <th>ID</th>
7
- <th>Failures</th>
7
+ <th>Run at</th>
8
8
  <th>Job</th>
9
+ <th>Arguments</th>
9
10
  <th>Queue</th>
10
- <th>Error</th>
11
- <th></th>
11
+ <% if params[:status] == 'failing' %>
12
+ <th>Failures</th>
13
+ <th>Error</th>
14
+ <% end %>
15
+ <% if %w[failing scheduled].include?(params[:status]) %>
16
+ <th></th>
17
+ <% end %>
12
18
  </tr>
13
19
  </thead>
14
20
  <tbody>
15
21
  <% @jobs.each do |job| %>
16
22
  <tr>
17
- <td><%= job[:id] %></td>
18
- <td><%= job[:error_count] %></td>
23
+ <td><%= link_to job[:id], job_path(job[:id]) %></td>
24
+ <td><%= job[:run_at].utc %></td>
19
25
  <td><%= humanized_job_class(job) %></td>
20
26
  <td><%= job[:queue] %></td>
21
- <td><%= format_error(job) %></td>
22
27
  <td>
23
- <%= button_to 'Delete', job_path(job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
28
+ <% job.dig(:args, 0, :arguments).each do |argument| %>
29
+ <p><%= argument %></p>
30
+ <% end %>
24
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">
38
+ <%= button_to 'Run', job_path(job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
39
+ <%= 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 %>
25
42
  </tr>
26
43
  <% end %>
27
44
  </tbody>
@@ -30,6 +47,9 @@
30
47
  <p>No jobs found</p>
31
48
  <% end %>
32
49
  </div>
33
- <% if @jobs_amount.positive? %>
34
- <%= 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?')" %>
50
+ <% if %w[failing scheduled].include?(params[:status]) && @jobs_amount.positive? %>
51
+ <div class="row">
52
+ <%= 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
+ <%= 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
+ </div>
35
55
  <% end %>
@@ -0,0 +1,51 @@
1
+ <div class="row">
2
+ <div class="columns">
3
+ <h1>Job <%= @job[:id] %></h1>
4
+ </div>
5
+ </div>
6
+ <div class="row">
7
+ <table cellspacing="0">
8
+ <tbody>
9
+ <tr>
10
+ <th>Job</th>
11
+ <td><%= humanized_job_class(@job) %></td>
12
+ </tr>
13
+ <tr>
14
+ <th>Queue</th>
15
+ <td><%= @job[:queue] %></td>
16
+ </tr>
17
+ <tr>
18
+ <th>Priority</th>
19
+ <td><%= @job[:priority] %></td>
20
+ </tr>
21
+ <tr>
22
+ <th>Run at</th>
23
+ <td><%= @job[:run_at].utc %></td>
24
+ </tr>
25
+ <tr>
26
+ <th>Failures</th>
27
+ <td><%= @job[:error_count] %></td>
28
+ </tr>
29
+ <tr>
30
+ <th>Args</th>
31
+ <td>
32
+ <% @job.dig(:args, 0, :arguments).each do |argument| %>
33
+ <p><%= argument %></p>
34
+ <% end %>
35
+ </td>
36
+ </tr>
37
+ <tr>
38
+ <th>Last Error</th>
39
+ <td>
40
+ <%= format_error(@job) %>
41
+ </td>
42
+ </tr>
43
+ </tbody>
44
+ </table>
45
+ </div>
46
+ <div class="row">
47
+ <div class="actions">
48
+ <%= button_to 'Run', job_path(@job[:id]), class: 'btn-danger', method: :patch, onclick: "return confirm('Are you sure you wish to reschedule job?')" %>
49
+ <%= button_to 'Delete', job_path(@job[:id]), class: 'btn-danger', method: :delete, onclick: "return confirm('Are you sure you wish to delete job?')" %>
50
+ </div>
51
+ </div>
@@ -1,22 +1,26 @@
1
1
  <div class="row">
2
2
  <div class="column">
3
3
  <div class="dashboard-stat running">
4
- <div class="cell">
5
- <h2>Running</h2>
6
- <span class="dashboard-value">
7
- <%= @dashboard_stats[:running] %>
8
- </span>
9
- </div>
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 %>
10
12
  </div>
11
13
  </div>
12
14
  <div class="column">
13
15
  <div class="dashboard-stat scheduled">
14
- <div class="cell">
15
- <h2>Scheduled</h2>
16
- <span class="dashboard-value">
17
- <%= @dashboard_stats[:scheduled] %>
18
- </span>
19
- </div>
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 %>
20
24
  </div>
21
25
  </div>
22
26
  <div class="column">
data/config/routes.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Que::View::Engine.routes.draw do
4
- resources :jobs, only: %i[index destroy] do
4
+ resources :jobs, only: %i[index show update destroy] do
5
+ post :reschedule_all, on: :collection
5
6
  delete :destroy_all, on: :collection
6
7
  end
7
8
 
data/lib/que/view/dsl.rb CHANGED
@@ -2,23 +2,52 @@
2
2
 
3
3
  module Que
4
4
  module View
5
+ # rubocop: disable Metrics/ClassLength
5
6
  class DSL
6
- def dashboard_stats(...)
7
- execute(dashboard_stats_sql(...))
7
+ def fetch_dashboard_stats(...)
8
+ execute(fetch_dashboard_stats_sql(...))
8
9
  end
9
10
 
10
- def failing_jobs(...)
11
- execute(failing_jobs_sql(...))
11
+ def fetch_failing_jobs(...)
12
+ execute(fetch_failing_jobs_sql(...))
12
13
  end
13
14
 
14
- def delete_all_failing_jobs
15
- execute(delete_jobs_query(lock_all_failing_jobs_sql))
15
+ def fetch_scheduled_jobs(...)
16
+ execute(fetch_scheduled_jobs_sql(...))
17
+ end
18
+
19
+ def fetch_job(...)
20
+ execute(fetch_job_sql(...))
21
+ end
22
+
23
+ def delete_failing_jobs
24
+ execute(delete_jobs_sql(lock_failing_jobs_sql))
25
+ end
26
+
27
+ def delete_scheduled_jobs
28
+ execute(delete_jobs_sql(lock_scheduled_jobs_sql))
29
+ end
30
+
31
+ def delete_job(...)
32
+ execute(delete_jobs_sql(lock_job_sql(...)))
33
+ end
34
+
35
+ def reschedule_scheduled_jobs(time)
36
+ execute(reschedule_jobs_sql(lock_scheduled_jobs_sql, time))
37
+ end
38
+
39
+ def reschedule_failing_jobs(time)
40
+ execute(reschedule_jobs_sql(lock_failing_jobs_sql, time))
41
+ end
42
+
43
+ def reschedule_job(job_id, time)
44
+ execute(reschedule_jobs_sql(lock_job_sql(job_id), time))
16
45
  end
17
46
 
18
47
  private
19
48
 
20
49
  # rubocop: disable Metrics/MethodLength
21
- def dashboard_stats_sql(search)
50
+ def fetch_dashboard_stats_sql(search)
22
51
  <<-SQL.squish
23
52
  SELECT count(*) AS total,
24
53
  count(locks.job_id) AS running,
@@ -36,7 +65,7 @@ module Que
36
65
  SQL
37
66
  end
38
67
 
39
- def failing_jobs_sql(per_page, offset, search)
68
+ def fetch_failing_jobs_sql(per_page, offset, search)
40
69
  <<-SQL.squish
41
70
  SELECT que_jobs.*
42
71
  FROM que_jobs
@@ -57,6 +86,36 @@ module Que
57
86
  SQL
58
87
  end
59
88
 
89
+ def fetch_scheduled_jobs_sql(per_page, offset, search)
90
+ <<-SQL.squish
91
+ SELECT que_jobs.*
92
+ FROM que_jobs
93
+ LEFT JOIN (
94
+ SELECT (classid::bigint << 32) + objid::bigint AS job_id
95
+ FROM pg_locks
96
+ WHERE locktype = 'advisory'
97
+ ) 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
+ )
104
+ ORDER BY run_at, id
105
+ LIMIT #{per_page}::int
106
+ OFFSET #{offset}::int
107
+ SQL
108
+ end
109
+
110
+ def fetch_job_sql(job_id)
111
+ <<-SQL.squish
112
+ SELECT *
113
+ FROM que_jobs
114
+ WHERE id = #{job_id}::bigint
115
+ LIMIT 1
116
+ SQL
117
+ end
118
+
60
119
  def delete_jobs_sql(scope)
61
120
  <<-SQL.squish
62
121
  WITH target AS (#{scope})
@@ -68,18 +127,47 @@ module Que
68
127
  SQL
69
128
  end
70
129
 
71
- def lock_all_failing_jobs_sql
130
+ def reschedule_jobs_sql(scope, time)
131
+ <<-SQL.squish
132
+ WITH target AS (#{scope})
133
+ UPDATE que_jobs
134
+ SET run_at = '#{time}'::timestamptz, expired_at = NULL
135
+ FROM target
136
+ WHERE target.locked
137
+ AND target.id = que_jobs.id
138
+ RETURNING pg_advisory_unlock(target.id)
139
+ SQL
140
+ end
141
+
142
+ def lock_failing_jobs_sql
72
143
  <<-SQL.squish
73
144
  SELECT id, pg_try_advisory_lock(id) AS locked
74
145
  FROM que_jobs
75
146
  WHERE error_count > 0
76
147
  SQL
77
148
  end
149
+
150
+ def lock_scheduled_jobs_sql
151
+ <<-SQL.squish
152
+ SELECT id, pg_try_advisory_lock(id) AS locked
153
+ FROM que_jobs
154
+ WHERE error_count = 0
155
+ SQL
156
+ end
157
+
158
+ def lock_job_sql(job_id)
159
+ <<-SQL.squish
160
+ SELECT id, pg_try_advisory_lock(id) AS locked
161
+ FROM que_jobs
162
+ WHERE id = #{job_id}::bigint
163
+ SQL
164
+ end
78
165
  # rubocop: enable Metrics/MethodLength
79
166
 
80
167
  def execute(sql)
81
168
  Que.execute(sql)
82
169
  end
83
170
  end
171
+ # rubocop: enable Metrics/ClassLength
84
172
  end
85
173
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Que
4
4
  module View
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.1'
6
6
  end
7
7
  end
data/lib/que/view.rb CHANGED
@@ -34,6 +34,8 @@ module Que
34
34
 
35
35
  # Public: All the methods delegated to instance. These should match the interface of Que::View::DSL.
36
36
  def_delegators :instance,
37
- :dashboard_stats, :failing_jobs, :delete_all_failing_jobs
37
+ :fetch_dashboard_stats, :fetch_failing_jobs, :fetch_scheduled_jobs, :fetch_job,
38
+ :delete_failing_jobs, :delete_scheduled_jobs, :delete_job,
39
+ :reschedule_scheduled_jobs, :reschedule_failing_jobs, :reschedule_job
38
40
  end
39
41
  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.1.0
4
+ version: 0.2.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-11-14 00:00:00.000000000 Z
11
+ date: 2023-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: que
@@ -168,6 +168,7 @@ files:
168
168
  - app/helpers/que/view/application_helper.rb
169
169
  - app/views/layouts/que/view/application.html.erb
170
170
  - app/views/que/view/jobs/index.html.erb
171
+ - app/views/que/view/jobs/show.html.erb
171
172
  - app/views/que/view/welcome/index.html.erb
172
173
  - config/routes.rb
173
174
  - lib/que/view.rb