sidekiq-hierarchy 0.1.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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -0
  7. data/CHANGELOG.md +14 -0
  8. data/CONTRIBUTING.md +57 -0
  9. data/Gemfile +7 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +396 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +7 -0
  15. data/img/dashboard.png +0 -0
  16. data/img/failed_workflow.png +0 -0
  17. data/img/in_progress_workflow.png +0 -0
  18. data/img/job.png +0 -0
  19. data/img/workflow_set.png +0 -0
  20. data/lib/sidekiq-hierarchy.rb +1 -0
  21. data/lib/sidekiq/hierarchy.rb +105 -0
  22. data/lib/sidekiq/hierarchy/callback_registry.rb +33 -0
  23. data/lib/sidekiq/hierarchy/client/middleware.rb +23 -0
  24. data/lib/sidekiq/hierarchy/faraday/middleware.rb +16 -0
  25. data/lib/sidekiq/hierarchy/http.rb +8 -0
  26. data/lib/sidekiq/hierarchy/job.rb +290 -0
  27. data/lib/sidekiq/hierarchy/notifications.rb +8 -0
  28. data/lib/sidekiq/hierarchy/observers.rb +9 -0
  29. data/lib/sidekiq/hierarchy/observers/job_update.rb +15 -0
  30. data/lib/sidekiq/hierarchy/observers/workflow_update.rb +18 -0
  31. data/lib/sidekiq/hierarchy/rack/middleware.rb +27 -0
  32. data/lib/sidekiq/hierarchy/server/middleware.rb +62 -0
  33. data/lib/sidekiq/hierarchy/version.rb +5 -0
  34. data/lib/sidekiq/hierarchy/web.rb +149 -0
  35. data/lib/sidekiq/hierarchy/workflow.rb +130 -0
  36. data/lib/sidekiq/hierarchy/workflow_set.rb +134 -0
  37. data/sidekiq-hierarchy.gemspec +33 -0
  38. data/web/views/_job_progress_bar.erb +28 -0
  39. data/web/views/_job_table.erb +37 -0
  40. data/web/views/_job_timings.erb +10 -0
  41. data/web/views/_progress_bar.erb +8 -0
  42. data/web/views/_search_bar.erb +17 -0
  43. data/web/views/_summary_bar.erb +30 -0
  44. data/web/views/_workflow_progress_bar.erb +24 -0
  45. data/web/views/_workflow_set_clear.erb +7 -0
  46. data/web/views/_workflow_table.erb +33 -0
  47. data/web/views/_workflow_timings.erb +14 -0
  48. data/web/views/_workflow_tree.erb +82 -0
  49. data/web/views/_workflow_tree_node.erb +18 -0
  50. data/web/views/job.erb +12 -0
  51. data/web/views/not_found.erb +1 -0
  52. data/web/views/status.erb +120 -0
  53. data/web/views/workflow.erb +45 -0
  54. data/web/views/workflow_set.erb +3 -0
  55. metadata +225 -0
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sidekiq/hierarchy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sidekiq-hierarchy"
8
+ spec.version = Sidekiq::Hierarchy::VERSION
9
+ spec.authors = ["Anuj Das"]
10
+ spec.email = ["anujdas@gmail.com"]
11
+
12
+ spec.summary = %q{A set of sidekiq middlewares to track workflows consisting of multiple levels of sidekiq jobs}
13
+ spec.description = %q{A set of sidekiq middlewares to track workflows consisting of multiple levels of sidekiq jobs}
14
+ spec.homepage = 'https://www.github.com/anujdas/sidekiq-hierarchy'
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'sidekiq', '~> 3.3'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.10'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'rspec-its'
28
+ spec.add_development_dependency 'fakeredis'
29
+ spec.add_development_dependency 'rspec-sidekiq'
30
+
31
+ spec.add_development_dependency 'faraday'
32
+ spec.add_development_dependency 'rack'
33
+ end
@@ -0,0 +1,28 @@
1
+ <% status = job.status %>
2
+ <% enqueued_at = job.enqueued_at %>
3
+ <% run_at = job.run_at %>
4
+
5
+ <% if status == :enqueued %>
6
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, 1.0]]} %>
7
+
8
+ <% elsif status == :requeued %>
9
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:requeued, 1.0]]} %>
10
+
11
+ <% elsif status == :running %>
12
+ <% runtime = Time.now - enqueued_at %>
13
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
14
+ <% run_pct = 1.0 - enqueued_pct %>
15
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:running, run_pct]], active: true} %>
16
+
17
+ <% elsif status == :complete %>
18
+ <% runtime = job.complete_at - enqueued_at %>
19
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
20
+ <% run_pct = 1.0 - enqueued_pct %>
21
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:complete, run_pct]]} %>
22
+
23
+ <% elsif status == :failed %>
24
+ <% runtime = job.failed_at - enqueued_at %>
25
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
26
+ <% failed_pct = 1.0 - enqueued_pct %>
27
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:failed, failed_pct]]} %>
28
+ <% end %>
@@ -0,0 +1,37 @@
1
+ <table class="table table-hover table-bordered table-striped table-white">
2
+ <thead>
3
+ <tr>
4
+ <th>JID</th>
5
+ <th>Class</th>
6
+ <th>Queue</th>
7
+ <th>Status</th>
8
+ <th>Enqueued at</th>
9
+ <th>Run at</th>
10
+ <th>Completed at</th>
11
+ <th>Failed at</th>
12
+ </tr>
13
+ </thead>
14
+
15
+ <tbody>
16
+ <% jobs.each do |job| %>
17
+ <tr>
18
+ <td>
19
+ <a href="<%= job_url(job) %>">
20
+ <%= job.jid %>
21
+ </a>
22
+ </td>
23
+ <td><%= job.info['class'] %></td>
24
+ <td><%= job.info['queue'] %></td>
25
+ <td>
26
+ <span class="label label-<%= bootstrap_status(job.status) %>">
27
+ <%= job.status %>
28
+ </span>
29
+ </td>
30
+ <td><%= job.enqueued_at %></td>
31
+ <td><%= job.run_at %></td>
32
+ <td><%= job.complete_at %></td>
33
+ <td><%= job.failed_at %></td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
@@ -0,0 +1,10 @@
1
+ <% if run_at = job.run_at %>
2
+ <dl class="dl-horizontal">
3
+ <dt>Enqueued for</dt>
4
+ <dd><%= run_at - job.enqueued_at %> seconds</dd>
5
+ <% if run_at && finished_at = (job.complete_at || job.failed_at) %>
6
+ <dt>Ran for</dt>
7
+ <dd><%= finished_at - run_at %> seconds</dd>
8
+ <% end %>
9
+ </dl>
10
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%# input: local var 'bars', in the format [[status, width], ...]; local var 'active', true == moving bar %>
2
+ <div class="progress<%= ' progress-striped active' if !!locals[:active] %>">
3
+ <% bars.each do |status, width| %>
4
+ <div class="progress-bar progress-bar-<%= bootstrap_status(status) %>" style="width: <%= width * 100 %>%">
5
+ <%= status.to_s.capitalize %>
6
+ </div>
7
+ <% end %>
8
+ </div>
@@ -0,0 +1,17 @@
1
+ <div class="row col-sm-12" style="margin: 20px 0">
2
+ <form class="form-inline col-sm-6" action="<%= workflow_url %>" method="get">
3
+ <div class="form-group">
4
+ <label for="workflow_jid" class="sr-only">Workflow ID</label>
5
+ <input type="search" class="form-control" name="workflow_jid" placeholder="Enter Workflow ID" size="24">
6
+ </div>
7
+ <button type="submit" class="btn btn-default">Search workflows</button>
8
+ </form>
9
+
10
+ <form class="form-inline col-sm-6" action="<%= job_url %>" method="get">
11
+ <div class="form-group">
12
+ <label for="jid" class="sr-only">Job ID</label>
13
+ <input type="search" class="form-control" name="jid" placeholder="Enter Job ID" size="24">
14
+ </div>
15
+ <button type="submit" class="btn btn-default">Search jobs</button>
16
+ </form>
17
+ </div>
@@ -0,0 +1,30 @@
1
+ <div class="col-sm-12 summary_bar">
2
+ <ul class="list-unstyled summary row">
3
+ <li class="busy col-sm-4" style="width: 33%">
4
+ <a href="<%= workflow_set_url(:running) %>">
5
+ <span class="count">
6
+ <%= Sidekiq::Hierarchy::RunningSet.new.size %>
7
+ </span>
8
+ <span class="desc">Running</span>
9
+ </a>
10
+ </li>
11
+
12
+ <li class="processed col-sm-4" style="width: 33%">
13
+ <a href="<%= workflow_set_url(:complete) %>">
14
+ <span class="count">
15
+ <%= Sidekiq::Hierarchy::CompleteSet.new.size %>
16
+ </span>
17
+ <span class="desc">Complete</span>
18
+ </a>
19
+ </li>
20
+
21
+ <li class="dead col-sm-4" style="width: 33%">
22
+ <a href="<%= workflow_set_url(:failed) %>">
23
+ <span class="count">
24
+ <%= Sidekiq::Hierarchy::FailedSet.new.size %>
25
+ </span>
26
+ <span class="desc">Failed</span>
27
+ </a>
28
+ </li>
29
+ </ul>
30
+ </div>
@@ -0,0 +1,24 @@
1
+ <% enqueued_at = workflow.enqueued_at %>
2
+ <% run_at = workflow.run_at %>
3
+
4
+ <% if workflow.root.enqueued? %>
5
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, 1.0]]} %>
6
+
7
+ <% elsif workflow.running? %>
8
+ <% runtime = Time.now - enqueued_at %>
9
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
10
+ <% run_pct = 1.0 - enqueued_pct %>
11
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:running, run_pct]], active: true} %>
12
+
13
+ <% elsif complete_at = workflow.complete_at %>
14
+ <% runtime = complete_at - enqueued_at %>
15
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
16
+ <% run_pct = 1.0 - enqueued_pct %>
17
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:complete, run_pct]]} %>
18
+
19
+ <% elsif failed_at = workflow.failed_at %>
20
+ <% runtime = failed_at - enqueued_at %>
21
+ <% enqueued_pct = (run_at - enqueued_at) / runtime %>
22
+ <% failed_pct = 1.0 - enqueued_pct %>
23
+ <%= erb sidekiq_hierarchy_template(:_progress_bar), locals: {bars: [[:enqueued, enqueued_pct], [:failed, failed_pct]]} %>
24
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <form action="<%= workflow_set_url(status) %>" method="post">
2
+ <div style="margin:0;padding:0">
3
+ <input name="_method" type="hidden" value="delete" />
4
+ <input name="authenticity_token" type="hidden" value="<%= session[:csrf] %>">
5
+ </div>
6
+ <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure? This is irreversible and potentially very expensive')">Clear Set</button>
7
+ </form>
@@ -0,0 +1,33 @@
1
+ <div class="table_container">
2
+ <table class="table table-hover table-bordered table-striped table-white">
3
+ <thead>
4
+ <tr>
5
+ <th>Workflow ID</th>
6
+ <th>Status</th>
7
+ <th>Enqueued at</th>
8
+ <th>Run at</th>
9
+ <th>Completed at</th>
10
+ <th>Failed at</th>
11
+ <th>Jobs</th>
12
+ </tr>
13
+ </thead>
14
+
15
+ <tbody>
16
+ <% workflows.each do |workflow| %>
17
+ <tr>
18
+ <td>
19
+ <a href="<%= workflow_url(workflow) %>">
20
+ <%= workflow.jid %>
21
+ </a>
22
+ </td>
23
+ <td><%= workflow.status %></td>
24
+ <td><%= workflow.enqueued_at %></td>
25
+ <td><%= workflow.run_at %></td>
26
+ <td><%= workflow.complete_at %></td>
27
+ <td><%= workflow.failed_at %></td>
28
+ <td><%= workflow.jobs.count %></td>
29
+ </tr>
30
+ <% end%>
31
+ </tbody>
32
+ </table>
33
+ </div>
@@ -0,0 +1,14 @@
1
+ <% if run_at = workflow.run_at %>
2
+ <dl class="dl-horizontal">
3
+ <% if workflow.complete? %>
4
+ <dt>Ran for</dt>
5
+ <dd><%= workflow.complete_at - run_at %> seconds</dd>
6
+ <% elsif workflow.failed? %>
7
+ <dt>Failed in</dt>
8
+ <dd><%= workflow.failed_at - run_at %> seconds</dd>
9
+ <% else %>
10
+ <dt>Running for</dt>
11
+ <dd><%= Time.now - run_at %> seconds</dd>
12
+ <% end %>
13
+ </dl>
14
+ <% end %>
@@ -0,0 +1,82 @@
1
+ <style>
2
+ /* Lifted from http://www.cssscript.com/demo/minimalist-tree-view-in-pure-css */
3
+ /* Alternative styling: http://www.goocode.net/css/153-pure-css-to-create-family-tree.html */
4
+
5
+ ul.workflow_tree {
6
+ padding-left: 0px;
7
+ }
8
+
9
+ .workflow_tree li {
10
+ list-style-type: none;
11
+ margin: 10px;
12
+ position: relative;
13
+ }
14
+
15
+ .workflow_tree li::before {
16
+ content: "";
17
+ position: absolute;
18
+ top: -7px;
19
+ left: -20px;
20
+ border-left: 1px solid #ccc;
21
+ border-bottom: 1px solid #ccc;
22
+ border-radius: 0 0 0 0px;
23
+ width: 20px;
24
+ height: 15px;
25
+ }
26
+
27
+ .workflow_tree li::after {
28
+ position: absolute;
29
+ content: "";
30
+ top: 8px;
31
+ left: -20px;
32
+ border-left: 1px solid #ccc;
33
+ border-top: 1px solid #ccc;
34
+ border-radius: 0px 0 0 0;
35
+ width: 20px;
36
+ height: 100%;
37
+ }
38
+
39
+ .workflow_tree li:last-child::after {
40
+ display: none;
41
+ }
42
+
43
+ .workflow_tree li:last-child:before{
44
+ border-radius: 0 0 0 5px;
45
+ }
46
+
47
+ ul.workflow_tree > li:first-child::before {
48
+ display: none;
49
+ }
50
+
51
+ ul.workflow_tree > li:first-child::after {
52
+ border-radius: 5px 0 0 0;
53
+ }
54
+
55
+ .workflow_tree li a {
56
+ border-radius: 5px;
57
+ padding: 2px 5px;
58
+ }
59
+
60
+ .workflow_tree li a:hover, .workflow_tree li a:focus {
61
+ background: #ccc;
62
+ color: #000;
63
+ text-decoration: none;
64
+ }
65
+
66
+ .workflow_tree li a:hover+ul li a, .workflow_tree li a:focus+ul li a {
67
+ color: #000;
68
+ }
69
+
70
+ .workflow_tree li a:hover+ul li::after, .workflow_tree li a:focus+ul li::after,
71
+ .workflow_tree li a:hover+ul li::before, .workflow_tree li a:focus+ul li::before
72
+ .workflow_tree li a:hover+ul::before, .workflow_tree li a:focus+ul::before
73
+ .workflow_tree li a:hover+ul ul::before, .workflow_tree li a:focus+ul ul::before{
74
+ border-color: #000;
75
+ }
76
+ </style>
77
+
78
+ <ul class="workflow_tree">
79
+ <% unless root.root? %><ul><% end %>
80
+ <%= erb sidekiq_hierarchy_template(:_workflow_tree_node), locals: {job: root} %>
81
+ <% unless root.root? %></ul><% end %>
82
+ </ul>
@@ -0,0 +1,18 @@
1
+ <li>
2
+ <a href="<%= root_path %>hierarchy/jobs/<%= job.jid %>">
3
+ <span title="<%= job.jid %>">
4
+ <%= job.info['class'] %>
5
+ </span>
6
+ (<%= job.info['queue'] %>)
7
+ <span class="label label-<%= bootstrap_status(job.status) %>">
8
+ <%= job.status %> <%= relative_time(status_updated_at(job)) %>
9
+ </span>
10
+ </a>
11
+ <% unless job.leaf? %>
12
+ <ul>
13
+ <% job.children.each do |child| %>
14
+ <%= erb sidekiq_hierarchy_template(:_workflow_tree_node), locals: {job: child} %>
15
+ <% end %>
16
+ </ul>
17
+ <% end %>
18
+ </li>
data/web/views/job.erb ADDED
@@ -0,0 +1,12 @@
1
+ <a href="<%= workflow_url(@workflow) %>">
2
+ <h3>Workflow <%= @workflow.jid %></h3>
3
+ </a>
4
+ <h4>Job <%= @job.jid %></h4>
5
+
6
+ <%= erb sidekiq_hierarchy_template(:_job_progress_bar), locals: {job: @job} %>
7
+
8
+ <%= erb sidekiq_hierarchy_template(:_job_table), locals: {jobs: [@job]} %>
9
+ <%= erb sidekiq_hierarchy_template(:_job_timings), locals: {job: @job} %>
10
+
11
+ <h4>Job Tree</h4>
12
+ <%= erb sidekiq_hierarchy_template(:_workflow_tree), locals: {root: @job} %>
@@ -0,0 +1 @@
1
+ <h2>404 NOT FOUND</h2>
@@ -0,0 +1,120 @@
1
+ <h3>Sidekiq Hierarchy</h3>
2
+
3
+ <%= erb sidekiq_hierarchy_template(:_summary_bar) %>
4
+ <%= erb sidekiq_hierarchy_template(:_search_bar) %>
5
+
6
+ <div class="panel panel-info">
7
+ <div class="panel-heading">
8
+ <a href="<%= workflow_set_url(:running) %>">
9
+ <h4>Running Workflows</h4>
10
+ </a>
11
+ </div>
12
+
13
+ <div class="panel-body">
14
+ <div class="table_container">
15
+ <table class="table table-hover table-bordered table-striped table-white">
16
+ <thead>
17
+ <tr>
18
+ <th>Workflow ID</th>
19
+ <th>Enqueued at</th>
20
+ <th>Run at</th>
21
+ </tr>
22
+ </thead>
23
+
24
+ <tbody>
25
+ <% @running.each do |workflow| %>
26
+ <tr>
27
+ <td>
28
+ <a href="<%= workflow_url(workflow) %>">
29
+ <%= workflow.jid %>
30
+ </a>
31
+ </td>
32
+ <td><%= relative_time(workflow.enqueued_at) %></td>
33
+ <td><%= relative_time(workflow.run_at) if workflow.run_at %></td>
34
+ </tr>
35
+ <% end%>
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="panel panel-success">
43
+ <div class="panel-heading">
44
+ <a href="<%= workflow_set_url(:complete) %>">
45
+ <h4>Complete Workflows</h4>
46
+ </a>
47
+ </div>
48
+
49
+ <div class="panel-body">
50
+ <div class="table_container">
51
+ <table class="table table-hover table-bordered table-striped table-white">
52
+ <thead>
53
+ <tr>
54
+ <th>Workflow ID</th>
55
+ <th>Enqueued at</th>
56
+ <th>Run at</th>
57
+ <th>Completed at</th>
58
+ </tr>
59
+ </thead>
60
+
61
+ <tbody>
62
+ <% @complete.each do |workflow| %>
63
+ <tr>
64
+ <td>
65
+ <a href="<%= workflow_url(workflow) %>">
66
+ <%= workflow.jid %>
67
+ </a>
68
+ </td>
69
+ <td><%= relative_time(workflow.enqueued_at) %></td>
70
+ <td><%= relative_time(workflow.run_at) %></td>
71
+ <td><%= relative_time(workflow.complete_at) %></td>
72
+ </tr>
73
+ <% end%>
74
+ </tbody>
75
+ </table>
76
+ </div>
77
+
78
+ <%= erb sidekiq_hierarchy_template(:_workflow_set_clear), locals: {status: :complete} %>
79
+ </div>
80
+ </div>
81
+
82
+ <div class="panel panel-danger">
83
+ <div class="panel-heading">
84
+ <a href="<%= workflow_set_url(:failed) %>">
85
+ <h4>Failed Workflows</h4>
86
+ </a>
87
+ </div>
88
+
89
+ <div class="panel-body">
90
+ <div class="table_container">
91
+ <table class="table table-hover table-bordered table-striped table-white">
92
+ <thead>
93
+ <tr>
94
+ <th>Workflow ID</th>
95
+ <th>Enqueued at</th>
96
+ <th>Run at</th>
97
+ <th>Failed at</th>
98
+ </tr>
99
+ </thead>
100
+
101
+ <tbody>
102
+ <% @failed.each do |workflow| %>
103
+ <tr>
104
+ <td>
105
+ <a href="<%= workflow_url(workflow) %>">
106
+ <%= workflow.jid %>
107
+ </a>
108
+ </td>
109
+ <td><%= relative_time(workflow.enqueued_at) %></td>
110
+ <td><%= relative_time(workflow.run_at) %></td>
111
+ <td><%= relative_time(workflow.failed_at) %></td>
112
+ </tr>
113
+ <% end %>
114
+ </tbody>
115
+ </table>
116
+ </div>
117
+
118
+ <%= erb sidekiq_hierarchy_template(:_workflow_set_clear), locals: {status: :failed} %>
119
+ </div>
120
+ </div>