que-web 0.1.0

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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE.txt +28 -0
  5. data/README.md +40 -0
  6. data/Rakefile +7 -0
  7. data/doc/queweb.png +0 -0
  8. data/examples/rack/Gemfile +5 -0
  9. data/examples/rack/Gemfile.lock +29 -0
  10. data/examples/rack/boot.rb +34 -0
  11. data/examples/rack/config.ru +39 -0
  12. data/lib/que/web/version.rb +0 -0
  13. data/lib/que/web/viewmodels/dashboard.rb +21 -0
  14. data/lib/que/web/viewmodels/job.rb +14 -0
  15. data/lib/que/web/viewmodels/job_list.rb +29 -0
  16. data/lib/que/web/viewmodels.rb +3 -0
  17. data/lib/que/web.rb +48 -0
  18. data/que-web.gemspec +26 -0
  19. data/spec/spec_helper.rb +17 -0
  20. data/spec/viewmodels/dashboard_spec.rb +25 -0
  21. data/spec/viewmodels/job_list_spec.rb +49 -0
  22. data/spec/viewmodels/job_spec.rb +19 -0
  23. data/web/public/fonts/FontAwesome.otf +0 -0
  24. data/web/public/fonts/fontawesome-webfont.eot +0 -0
  25. data/web/public/fonts/fontawesome-webfont.svg +520 -0
  26. data/web/public/fonts/fontawesome-webfont.ttf +0 -0
  27. data/web/public/fonts/fontawesome-webfont.woff +0 -0
  28. data/web/public/js/foundation.min.js +3651 -0
  29. data/web/public/js/vendor/fastclick.js +9 -0
  30. data/web/public/js/vendor/jquery.cookie.js +8 -0
  31. data/web/public/js/vendor/jquery.js +26 -0
  32. data/web/public/js/vendor/modernizr.js +8 -0
  33. data/web/public/js/vendor/placeholder.js +2 -0
  34. data/web/public/styles/application.css +55 -0
  35. data/web/public/styles/font-awesome.min.css +4 -0
  36. data/web/public/styles/foundation.min.css +1 -0
  37. data/web/public/styles/normalize.css +427 -0
  38. data/web/views/_footer.erb +10 -0
  39. data/web/views/_navbar.erb +14 -0
  40. data/web/views/failing.erb +41 -0
  41. data/web/views/index.erb +37 -0
  42. data/web/views/layout.erb +14 -0
  43. metadata +145 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d51e478e11b1c1e255dcc5ae5451d4a6b5e7475f
4
+ data.tar.gz: 1e8ff161785552d15151a7d60dc76c270b55e873
5
+ SHA512:
6
+ metadata.gz: e454491b1c4c596ab04d4157b4b97d5e0d94cd095d8c1a903e18e51a3ca0a37f0423bf08e635e2df3e3b5f6462639e0928842bf63a7c0d4da78c9253db086269
7
+ data.tar.gz: 7cc37957dedac3a77fca6ab261fc96c3e0500f06bfea3c28494ada87078ad06bdc3b5dace98dafe190b3261e0bda820dd89b37de7f620d2098e3093b2533101e
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.bundle
10
+ *.so
11
+ *.o
12
+ *.a
13
+ mkmf.log
14
+ vendor/bundle
15
+ examples/rack/vendor/bundle
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,28 @@
1
+ Copyright (c) 2014, Jason Staten
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its contributors
15
+ may be used to endorse or promote products derived from this software
16
+ without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
28
+ DAMAGE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # que-web
2
+
3
+ que-web is a web UI to the [Que](https://github.com/chanks/que) job queue.
4
+
5
+ ![Que Web](https://raw.githubusercontent.com/statianzo/que-web/master/doc/queweb.png)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'que-web'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install que-web
22
+
23
+ ## Usage
24
+
25
+ In your `config.ru` add
26
+
27
+ ```ruby
28
+ require "que/web"
29
+
30
+ map "/que" do
31
+ run Que::Web
32
+ end
33
+ ```
34
+
35
+ Or in Rails `config/routes.rb`
36
+
37
+ ```ruby
38
+ require "que/web"
39
+ mount Que::Web => "/que"
40
+ ```
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs = ['spec', 'lib']
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ end
data/doc/queweb.png ADDED
Binary file
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "pg"
4
+ gem "sequel"
5
+ gem "que-web", :path => "../../"
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: ../../
3
+ specs:
4
+ que-web (0.1.0)
5
+ que (~> 0.8)
6
+ sinatra
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ pg (0.17.1)
12
+ que (0.8.2)
13
+ rack (1.5.2)
14
+ rack-protection (1.5.3)
15
+ rack
16
+ sequel (4.16.0)
17
+ sinatra (1.4.5)
18
+ rack (~> 1.4)
19
+ rack-protection (~> 1.4)
20
+ tilt (~> 1.3, >= 1.3.4)
21
+ tilt (1.4.1)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ pg
28
+ que-web!
29
+ sequel
@@ -0,0 +1,34 @@
1
+ require 'bundler/setup'
2
+ require 'sequel'
3
+ require 'que'
4
+ require 'logger'
5
+ require 'open-uri'
6
+ require 'securerandom'
7
+
8
+ Que.logger = Logger.new(STDOUT)
9
+ Que.logger.level = Logger::INFO
10
+ Que.connection = Sequel.connect "postgres://localhost/quewebtest", max_connections: Que.worker_count + 1
11
+ Que.migrate!
12
+ Que.mode = :async
13
+ $stdout.sync = true
14
+
15
+ class FailJob < Que::Job
16
+ class LameError < StandardError; end
17
+
18
+ def run(arg1, arg2)
19
+ raise LameError
20
+ end
21
+ end
22
+
23
+ class SuccessJob < Que::Job
24
+ def run(arg1, arg2)
25
+ sleep 0.5
26
+ end
27
+ end
28
+
29
+ class SlowJob < Que::Job
30
+ def run(arg1, arg2)
31
+ sleep 15
32
+ end
33
+ end
34
+
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+ require 'que/web'
3
+
4
+ map '/que' do
5
+ run Que::Web
6
+ end
7
+
8
+ map '/success' do
9
+ run lambda { |env|
10
+ SuccessJob.enqueue 'arg1', {name: 'foo', age: 10}
11
+ [200, {}, ['Success job enqueued']]
12
+ }
13
+ end
14
+
15
+ map '/fail' do
16
+ run lambda { |env|
17
+ FailJob.enqueue 'arg1', {name: 'fail', age: 20}
18
+ [200, {}, ['Failing job queued']]
19
+ }
20
+ end
21
+
22
+ map '/delay' do
23
+ run lambda { |env|
24
+ SuccessJob.enqueue 'arg1', {name: 'delay', age: 30}, run_at: Time.now + 300
25
+ [200, {}, ['Delayed job queued']]
26
+ }
27
+ end
28
+
29
+ map '/slow' do
30
+ run lambda { |env|
31
+ SlowJob.enqueue 'arg1', {name: 'delay', age: 30}
32
+ [200, {}, ['Slow job queued']]
33
+ }
34
+ end
35
+
36
+ run lambda { |env|
37
+ [200, {}, ['Hello']]
38
+ }
39
+
File without changes
@@ -0,0 +1,21 @@
1
+ module Que::Web::Viewmodels
2
+ class Dashboard
3
+ attr_reader :running, :scheduled, :failing
4
+ def initialize(job_stats, failing_count)
5
+ @running = calculate_running(job_stats)
6
+ @scheduled = calculate_scheduled(job_stats)
7
+ @failing = failing_count
8
+ end
9
+
10
+
11
+ private
12
+
13
+ def calculate_running(job_stats)
14
+ job_stats.map{|s| s["count_working"]}.reduce(0, :+)
15
+ end
16
+
17
+ def calculate_scheduled(job_stats)
18
+ job_stats.map{|s| s["count"]}.reduce(0, :+)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Que::Web::Viewmodels
2
+ class Job < Struct.new(
3
+ :args, :error_count, :job_class, :job_id, :last_error, :last_error,
4
+ :pg_backend_pid, :pg_last_query, :pg_last_query_started_at, :pg_state,
5
+ :pg_state_changed_at, :pg_transaction_started_at, :pg_waiting_on_lock,
6
+ :priority, :queue, :run_at)
7
+
8
+ def initialize(job)
9
+ members.each do |m|
10
+ self[m] = job[m.to_s]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ module Que::Web::Viewmodels
2
+ class JobList
3
+ PAGE_SIZE = 10
4
+
5
+ attr_reader :page_jobs, :total, :page
6
+
7
+ def initialize(page_jobs, total, page)
8
+ @page_jobs = page_jobs.map{|j| Job.new(j)}
9
+ @total = total
10
+ @page = page
11
+ end
12
+
13
+ def next_page
14
+ page.succ
15
+ end
16
+
17
+ def prev_page
18
+ page.pred
19
+ end
20
+
21
+ def has_next?
22
+ @page_jobs.length >= PAGE_SIZE
23
+ end
24
+
25
+ def has_prev?
26
+ @page > 0
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.expand_path('../viewmodels/*.rb', __FILE__)].each {|f| require f}
2
+ module Que::Web::Viewmodels
3
+ end
data/lib/que/web.rb ADDED
@@ -0,0 +1,48 @@
1
+ require "sinatra"
2
+
3
+ module Que
4
+ class Web < Sinatra::Base
5
+ use Rack::MethodOverride
6
+
7
+ set :root, File.expand_path("../../../web", __FILE__)
8
+ set :public_folder, proc { "#{root}/public" }
9
+ set :views, proc { File.expand_path("views", root) }
10
+
11
+ get "/" do
12
+ job_stats = Que.job_stats
13
+ failing_count = Que.execute("SELECT count(*) FROM que_jobs WHERE error_count > 0")[0]["count"]
14
+ @dashboard = Viewmodels::Dashboard.new(job_stats, failing_count)
15
+ erb :index
16
+ end
17
+
18
+ get "/failing" do
19
+ failing_count = Que.execute("SELECT count(*) FROM que_jobs WHERE error_count > 0")[0]["count"]
20
+ failing_jobs = Que.execute("SELECT * FROM que_jobs WHERE error_count > 0")
21
+ @list = Viewmodels::JobList.new(failing_jobs, failing_count, 0)
22
+ erb :failing
23
+ end
24
+
25
+ delete "/jobs/:id" do |id|
26
+ job_id = id.to_i
27
+ if job_id > 0
28
+ Que.execute "DELETE FROM que_jobs WHERE job_id = $1::bigint", [job_id]
29
+ end
30
+
31
+ redirect request.referrer, 303
32
+ end
33
+
34
+ helpers do
35
+ def root_path
36
+ "#{env['SCRIPT_NAME']}/"
37
+ end
38
+
39
+ def active_class(pattern)
40
+ if request.path.match pattern
41
+ "active"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ require "que/web/viewmodels"
data/que-web.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'que/web/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "que-web"
8
+ spec.version = "0.1.0"
9
+ spec.authors = ["Jason Staten"]
10
+ spec.email = ["jstaten07@gmail.com"]
11
+ spec.summary = %q{A web interface for the que queue}
12
+ spec.description = %q{A web interface for the que queue}
13
+ spec.homepage = "https://github.com/statianzo/que-web"
14
+ spec.license = "BSD"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "que", "~> 0.8"
22
+ spec.add_dependency "sinatra"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.6"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ end
@@ -0,0 +1,17 @@
1
+ require "bundler/setup"
2
+ require "que/web"
3
+ require "minitest/autorun"
4
+
5
+ class CustomFilter
6
+ def self.filter(bt)
7
+ return ['No backtrace'] unless bt
8
+
9
+ new_bt = bt.take_while { |line| line !~ %r{minitest} }
10
+ new_bt = bt.select { |line| line !~ %r{minitest} } if new_bt.empty?
11
+ new_bt = bt.dup if new_bt.empty?
12
+
13
+ new_bt
14
+ end
15
+ end
16
+
17
+ Minitest.backtrace_filter = CustomFilter
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe Que::Web::Viewmodels::Dashboard do
4
+ let(:job_stats) {
5
+ [
6
+ {"queue"=>"", "job_class"=>"FailJob", "count"=>8, "count_working"=>0, "count_errored"=>8, "highest_error_count"=>11, "oldest_run_at"=> Time.new(2014,11,12,6,0,0)},
7
+ {"queue"=>"", "job_class"=>"SuccessJob", "count"=>2, "count_working"=>2, "count_errored"=>2, "highest_error_count"=>0, "oldest_run_at"=> Time.new(2014,11,12,8,0,0)},
8
+ {"queue"=>"", "job_class"=>"OtherJob", "count"=>7, "count_working"=>4, "count_errored"=>2, "highest_error_count"=>0, "oldest_run_at"=> Time.new(2014,11,12,9,0,0)}
9
+ ]
10
+ }
11
+ let(:failing_count) { 7 }
12
+ let(:subject) { Que::Web::Viewmodels::Dashboard.new(job_stats, failing_count) }
13
+
14
+ it 'tallies running jobs' do
15
+ subject.running.must_equal 6
16
+ end
17
+
18
+ it 'tallies scheduled jobs' do
19
+ subject.scheduled.must_equal 17
20
+ end
21
+
22
+ it 'uses failing jobs' do
23
+ subject.failing.must_equal failing_count
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe Que::Web::Viewmodels::JobList do
4
+ let(:job) {
5
+ {"priority"=>100, "run_at"=> Time.now,
6
+ "job_id"=>555, "job_class"=>"SuccessJob",
7
+ "args"=>["arg1", {"name"=>"foo", "age"=>10}],
8
+ "error_count"=>0,
9
+ "last_error"=>nil,
10
+ "queue"=>"foo"
11
+ }
12
+ }
13
+ let(:subject) { Que::Web::Viewmodels::JobList.new([job], 1, 3) }
14
+
15
+ it "maps jobs" do
16
+ subject.page_jobs.length.must_equal 1
17
+ subject.page_jobs.first.queue.must_equal "foo"
18
+ end
19
+
20
+ it "provides next page" do
21
+ subject.next_page.must_equal 4
22
+ end
23
+
24
+ it "provides prevous page" do
25
+ subject.prev_page.must_equal 2
26
+ end
27
+
28
+ it "has next when full page" do
29
+ subject.page_jobs.concat [job] * 9
30
+ subject.has_next?.must_equal true
31
+ end
32
+
33
+ it "does not have next not full page" do
34
+ subject.has_next?.must_equal false
35
+ end
36
+
37
+ it "has prev page when greater than 0" do
38
+ subject.has_prev?.must_equal true
39
+ end
40
+
41
+ it "does not have prev page when equal to 0" do
42
+ list = subject.class.new([], 1, 0)
43
+ list.has_prev?.must_equal false
44
+ end
45
+
46
+ it "does not have next not full page" do
47
+ subject.has_next?.must_equal false
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe Que::Web::Viewmodels::Job do
4
+ let(:source_job) {
5
+ {"priority"=>100, "run_at"=> Time.now,
6
+ "job_id"=>555, "job_class"=>"SuccessJob",
7
+ "args"=>["arg1", {"name"=>"foo", "age"=>10}],
8
+ "error_count"=>0,
9
+ "last_error"=>nil,
10
+ "queue"=>"foo"
11
+ }
12
+ }
13
+ let(:subject) { Que::Web::Viewmodels::Job.new(source_job) }
14
+
15
+ it 'maps fields from source' do
16
+ subject.priority.must_equal source_job["priority"]
17
+ subject.queue.must_equal source_job["queue"]
18
+ end
19
+ end
Binary file