qujo 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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +38 -0
- data/app/assets/javascripts/qujo/application.js +15 -0
- data/app/assets/javascripts/qujo/qujo.js +124 -0
- data/app/assets/stylesheets/qujo/application.css +13 -0
- data/app/controllers/qujo/application_controller.rb +3 -0
- data/app/controllers/qujo/jobs_controller.rb +175 -0
- data/app/helpers/qujo/application_helper.rb +4 -0
- data/app/views/qujo/common/_bootstrap.html.erb +29 -0
- data/app/views/qujo/jobs/_form.html.erb +14 -0
- data/app/views/qujo/jobs/edit.html.erb +9 -0
- data/app/views/qujo/jobs/index.html.erb +54 -0
- data/app/views/qujo/jobs/new.html.erb +5 -0
- data/app/views/qujo/jobs/resque.html.erb +1 -0
- data/app/views/qujo/jobs/show.html.erb +46 -0
- data/config/routes.rb +13 -0
- data/lib/qujo.rb +34 -0
- data/lib/qujo/concerns/common.rb +117 -0
- data/lib/qujo/concerns/exceptions.rb +27 -0
- data/lib/qujo/concerns/logging.rb +36 -0
- data/lib/qujo/concerns/model.rb +36 -0
- data/lib/qujo/concerns/status.rb +27 -0
- data/lib/qujo/database/mongoid.rb +25 -0
- data/lib/qujo/engine.rb +6 -0
- data/lib/qujo/queue/resque.rb +29 -0
- data/lib/qujo/queue/resque/job_worker.rb +25 -0
- data/lib/qujo/queue/resque/schedule_worker.rb +30 -0
- data/lib/qujo/version.rb +9 -0
- data/lib/tasks/qujo_tasks.rake +4 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +65 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +31 -0
- data/test/dummy/config/environments/production.rb +64 -0
- data/test/dummy/config/environments/test.rb +35 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/integration/navigation_test.rb +9 -0
- data/test/qujo_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- metadata +189 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
<div>
|
2
|
+
<div class="container">
|
3
|
+
<div class="pull-left">
|
4
|
+
<ul class="nav nav-pills">
|
5
|
+
<li class="<%= "active" if request.path == jobs_path %>"><%= link_to 'Recent', jobs_path, class: "pill" %></li>
|
6
|
+
<li class="<%= "active" if request.path == errors_jobs_path %>"><%= link_to 'Errors', errors_jobs_path, class: "pill" %></li>
|
7
|
+
<li class="<%= "active" if request.path == all_jobs_path %>"><%= link_to 'All', all_jobs_path, class: "pill" %></li>
|
8
|
+
</ul>
|
9
|
+
</div>
|
10
|
+
<div class="buttons pull-right">
|
11
|
+
<%= link_to "#", class: "btn btn-primary job_accept_all" do %>
|
12
|
+
<i class="icon-ok-sign icon-white"></i> Accept All
|
13
|
+
<% end %>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<table class="table table-condensed table-striped tablesorter">
|
19
|
+
<thead>
|
20
|
+
<tr>
|
21
|
+
<th style="width: 100px;">Status</th>
|
22
|
+
<th style="width: 100px;">Class</th>
|
23
|
+
<th>Message</th>
|
24
|
+
<th>Created</th>
|
25
|
+
<th class="links"></th>
|
26
|
+
</tr>
|
27
|
+
</thead>
|
28
|
+
<tbody>
|
29
|
+
<% @jobs.each do |job| %>
|
30
|
+
<tr>
|
31
|
+
<td><span class="<%= "text-error" if job.status == :error %>"><%= job.status %></span></td>
|
32
|
+
<td><%= job.class.name %></td>
|
33
|
+
<td><%= link_to((job.message||"none")[0..50], job_path(job)) %></td>
|
34
|
+
<td><%= job.created_at %></td>
|
35
|
+
<td class="links">
|
36
|
+
<% if [:new, :working, :waiting].include?(job.status) %>
|
37
|
+
<%= link_to "#", data: {id: job.id.to_s||"nil"}, :title => "Cancel", :class => "btn btn-mini job_cancel" do %>
|
38
|
+
<i class="icon-remove-sign"></i>
|
39
|
+
<% end %>
|
40
|
+
<% end %>
|
41
|
+
<% unless job.accepted_at %>
|
42
|
+
<%= link_to "#", data: {id: job.id.to_s||"nil"}, :title => "Accept", :class => "btn btn-mini job_accept" do %>
|
43
|
+
<i class="icon-ok-sign"></i>
|
44
|
+
<% end %>
|
45
|
+
<% end %>
|
46
|
+
<%= link_to "#", :title => "Refresh", :class => "btn btn-mini job_retry", data: {id: job.id.to_s } do %>
|
47
|
+
<i class="icon-refresh"></i>
|
48
|
+
<% end %>
|
49
|
+
</td>
|
50
|
+
</tr>
|
51
|
+
<% end %>
|
52
|
+
</tbody>
|
53
|
+
</table>
|
54
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
<iframe height='600' src='/resque' style='border: none' width='100%'></iframe>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<% content_for :subnav_buttons do %>
|
2
|
+
<% end %>
|
3
|
+
<% content_for :subnav_pills do %>
|
4
|
+
<%= link_to 'Back', jobs_path, class: "btn" %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<table class="table table-condensed table-bordered fields-horizontal">
|
8
|
+
<tr>
|
9
|
+
<th>ID</th>
|
10
|
+
<td><%= @job.id %></td>
|
11
|
+
</tr>
|
12
|
+
<tr>
|
13
|
+
<th>Class</th>
|
14
|
+
<td><%= @job.class.name %></td>
|
15
|
+
</tr>
|
16
|
+
<tr>
|
17
|
+
<th>Status</th>
|
18
|
+
<td><%= @job.status %></td>
|
19
|
+
</tr>
|
20
|
+
<tr>
|
21
|
+
<th>Model</th>
|
22
|
+
<td>
|
23
|
+
<% if @job.model %>
|
24
|
+
<%= link_to "#{@job.model.display} (#{@job.model.class})", "#" %>
|
25
|
+
<% end %>
|
26
|
+
</td>
|
27
|
+
</tr>
|
28
|
+
<tr>
|
29
|
+
<th>Data</th>
|
30
|
+
<td>
|
31
|
+
<% if @job.data %>
|
32
|
+
<pre class="pre-scrollable"><%= @job.data.to_yaml %></pre>
|
33
|
+
<% end %></td>
|
34
|
+
</tr>
|
35
|
+
<tr>
|
36
|
+
<th>Message</th>
|
37
|
+
<td><%= @job.message %></td>
|
38
|
+
</tr>
|
39
|
+
<tr>
|
40
|
+
<th>Created</th>
|
41
|
+
<td><%= @job.created_at %></td>
|
42
|
+
</tr>
|
43
|
+
</table>
|
44
|
+
|
45
|
+
<%= render "common/log", log: @job.log, trace: @job.trace %>
|
46
|
+
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Qujo::Engine.routes.draw do
|
2
|
+
resources :jobs do
|
3
|
+
post "accept", on: :member
|
4
|
+
post "refresh", on: :member
|
5
|
+
get "resque", on: :collection if defined?(Resque)
|
6
|
+
get "status", on: :collection
|
7
|
+
get "errors", on: :collection
|
8
|
+
get "all", on: :collection
|
9
|
+
end
|
10
|
+
if defined?(Resque)
|
11
|
+
mount Resque::Server.new, :at => "/resque"
|
12
|
+
end
|
13
|
+
end
|
data/lib/qujo.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'qujo/version'
|
2
|
+
|
3
|
+
require 'qujo/concerns/common'
|
4
|
+
require 'qujo/concerns/logging'
|
5
|
+
require 'qujo/concerns/model'
|
6
|
+
require 'qujo/concerns/status'
|
7
|
+
|
8
|
+
if defined?(Rails)
|
9
|
+
require "qujo/engine"
|
10
|
+
end
|
11
|
+
|
12
|
+
module Qujo
|
13
|
+
class << self
|
14
|
+
def configure
|
15
|
+
@cfg ||= OpenStruct.new({
|
16
|
+
:ui_status => true,
|
17
|
+
:logger => Rails.logger,
|
18
|
+
:models => "Jobs"
|
19
|
+
})
|
20
|
+
yield @cfg if block_given?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def config
|
25
|
+
@cfg
|
26
|
+
end
|
27
|
+
|
28
|
+
def logger
|
29
|
+
config.logger
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Qujo.configure
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Qujo
|
2
|
+
module Concerns
|
3
|
+
module Common
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
extend ClassMethods
|
8
|
+
attr_accessor :model, :parent
|
9
|
+
|
10
|
+
def run
|
11
|
+
#self.load_model
|
12
|
+
#self.load_parent
|
13
|
+
|
14
|
+
self.status = :working
|
15
|
+
self.save!
|
16
|
+
|
17
|
+
self.work
|
18
|
+
|
19
|
+
self.status = :complete
|
20
|
+
self.save!
|
21
|
+
|
22
|
+
self.accept
|
23
|
+
rescue => e
|
24
|
+
error "JOB#RUN: error running job, attempting to save"
|
25
|
+
error " #{e.message} at #{e.backtrace.first}"
|
26
|
+
self.status = :error
|
27
|
+
self.message = e.message
|
28
|
+
self.trace = e.backtrace
|
29
|
+
self.save!
|
30
|
+
end
|
31
|
+
|
32
|
+
# wait while return value from block is true
|
33
|
+
def wait(options = {}, &block)
|
34
|
+
return unless block_given?
|
35
|
+
o = {interval: 3, maximum: 600}.merge(options)
|
36
|
+
interval = o[:interval]
|
37
|
+
maximum = o[:maximum]
|
38
|
+
count = 0
|
39
|
+
while ((count * interval) < maximum) && yield do
|
40
|
+
sleep interval
|
41
|
+
count += 1
|
42
|
+
end
|
43
|
+
raise "wait timeout count=#{count} interval=#{interval} maximum=#{maximum}" if ((count * interval) >= maximum)
|
44
|
+
end
|
45
|
+
|
46
|
+
#def model
|
47
|
+
# @model ||= begin
|
48
|
+
# if data && data["id"] && data["class"]
|
49
|
+
# begin
|
50
|
+
# c = data["class"].constantize
|
51
|
+
# c.find(data["id"]) # rescue nil #TODO: make this smarter
|
52
|
+
# #rescue Mongoid::Errors::DocumentNotFound => e
|
53
|
+
# # logger.error "document not found"
|
54
|
+
# # nil
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#end
|
59
|
+
#
|
60
|
+
#def parent
|
61
|
+
# @parent ||= begin
|
62
|
+
# if data["parent"] && data["parent"]["type"] && data["parent"]["id"]
|
63
|
+
# begin
|
64
|
+
# c = data["parent"]["type"].constantize
|
65
|
+
# c.find(data["parent"]["id"])
|
66
|
+
# #rescue Mongoid::Errors::DocumentNotFound => e
|
67
|
+
# # logger.error "document not found"
|
68
|
+
# # nil
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
#end
|
73
|
+
|
74
|
+
def model
|
75
|
+
@model ||= begin
|
76
|
+
if data && data["model"] && data["model"]["id"] && data["model"]["class"]
|
77
|
+
i = data["model"]["id"]
|
78
|
+
c = data["model"]["class"].constantize
|
79
|
+
c.find(i)
|
80
|
+
end
|
81
|
+
rescue => e
|
82
|
+
logger.error "could not load model: #{e.message} at #{e.backtrace.first}"
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def parent
|
88
|
+
@parent ||= begin
|
89
|
+
if data && data["parent"] && data["parent"]["id"] && data["parent"]["class"]
|
90
|
+
i = data["parent"]["id"]
|
91
|
+
c = data["parent"]["class"].constantize
|
92
|
+
c.find(i)
|
93
|
+
end
|
94
|
+
rescue => e
|
95
|
+
logger.error "could not load parent: #{e.message} at #{e.backtrace.first}"
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module ClassMethods
|
102
|
+
def errors?
|
103
|
+
where(status: :error).count > 0
|
104
|
+
end
|
105
|
+
|
106
|
+
def inherited(child)
|
107
|
+
child.instance_eval do
|
108
|
+
def model_name
|
109
|
+
::Job.model_name
|
110
|
+
end
|
111
|
+
end
|
112
|
+
super
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Qujo
|
2
|
+
module Exceptions
|
3
|
+
|
4
|
+
class ResqueNotLoaded < StandardError
|
5
|
+
def initialize(msg = "Resque is undefined")
|
6
|
+
super
|
7
|
+
end
|
8
|
+
end
|
9
|
+
class ResqueSchedulerNotLoaded < StandardError
|
10
|
+
def initialize(msg = "Resque::Scheduler is undefined")
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class MongoidUndefined < StandardError
|
16
|
+
def initialize(msg = "Mongoid is undefined")
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class JobNameUndefined < StandardError
|
22
|
+
def initialize(msg = "Job name must match Jobs::ModelName::ActionName or be passed as the :job option")
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Qujo
|
2
|
+
module Concerns
|
3
|
+
module Logging
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def logger
|
8
|
+
@logger ||= Qujo.logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def debug(msg)
|
12
|
+
_log(:debug, msg) if Rails.env.development?
|
13
|
+
end
|
14
|
+
|
15
|
+
def info(msg)
|
16
|
+
_log(:info, msg)
|
17
|
+
end
|
18
|
+
|
19
|
+
def warn(msg)
|
20
|
+
_log(:warn, msg)
|
21
|
+
end
|
22
|
+
|
23
|
+
def error(msg)
|
24
|
+
_log(:error, msg)
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def _log(sev, msg)
|
30
|
+
logger.send(sev, msg)
|
31
|
+
self.log << {severity: sev, message: msg}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Qujo
|
2
|
+
module Concerns
|
3
|
+
module Model
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def enqueue(action, options={})
|
8
|
+
name = options[:job] || "#{Qujo.config.models}::#{self.class.name.capitalize}::#{action.capitalize}"
|
9
|
+
raise Qujo::Exceptions::JobNameUndefined unless name
|
10
|
+
|
11
|
+
begin
|
12
|
+
klass = name.constantize
|
13
|
+
rescue => e
|
14
|
+
raise Qujo::Exceptions::JobNameUndefined, e.message
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
data = {}
|
19
|
+
data = data.merge(model: {id: self.id.to_s, class: self.class.name})
|
20
|
+
|
21
|
+
parent = options.delete(:parent)
|
22
|
+
if parent && parent.is_a?(::Job)
|
23
|
+
data = data.merge(parent: {id: parent.id.to_s, class: parent.class.name})
|
24
|
+
end
|
25
|
+
|
26
|
+
data = data.merge(options)
|
27
|
+
|
28
|
+
job = klass.create(data: data)
|
29
|
+
|
30
|
+
logger.info "** ENQUEUE: JOB=#{j.id} NAME=#{n} DATA=#{d}" if self.respond_to?(:logger)
|
31
|
+
job.enqueue
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Qujo
|
2
|
+
module Concerns
|
3
|
+
module Status
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def accept
|
8
|
+
self.accepted_at = Time.now
|
9
|
+
self.save!
|
10
|
+
end
|
11
|
+
|
12
|
+
def cancel
|
13
|
+
self.status = :cancelled
|
14
|
+
self.save!
|
15
|
+
end
|
16
|
+
|
17
|
+
def retry
|
18
|
+
self.accepted_at = nil
|
19
|
+
self.status = :retry
|
20
|
+
self.log << "--- retry ---"
|
21
|
+
self.message = nil
|
22
|
+
self.save
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Qujo
|
2
|
+
module Database
|
3
|
+
module Mongoid
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
raise Qujo::Exceptions::DatabaseUndefined, "Mongoid is undefined" unless defined?(Mongoid)
|
8
|
+
include ::Mongoid::Document
|
9
|
+
include ::Mongoid::Timestamps
|
10
|
+
include ::Mongoid::Symbolize
|
11
|
+
|
12
|
+
field :data, type: Hash
|
13
|
+
symbolize :status, in: [:new, :working, :waiting, :complete, :error, :retry, :cancelled], default: "new"
|
14
|
+
field :message, type: String
|
15
|
+
field :log, type: Array, default: []
|
16
|
+
field :trace, type: Array, default: []
|
17
|
+
field :accepted_at, type: DateTime
|
18
|
+
|
19
|
+
default_scope where(accepted_at: nil)
|
20
|
+
scope :active, -> { where(:status.in => [:new, :working, :waiting, :retry, :error], accepted_at: nil) }
|
21
|
+
scope :errors, -> { where(:status.in => [:error]) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|