gush 0.0.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 +7 -0
- data/.gitignore +21 -0
- data/.rspec +1 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +128 -0
- data/Rakefile +1 -0
- data/bin/gush +12 -0
- data/gush.gemspec +32 -0
- data/lib/gush.rb +47 -0
- data/lib/gush/cli.rb +245 -0
- data/lib/gush/client.rb +146 -0
- data/lib/gush/configuration.rb +42 -0
- data/lib/gush/errors.rb +3 -0
- data/lib/gush/job.rb +161 -0
- data/lib/gush/logger_builder.rb +15 -0
- data/lib/gush/metadata.rb +24 -0
- data/lib/gush/null_logger.rb +6 -0
- data/lib/gush/version.rb +3 -0
- data/lib/gush/worker.rb +100 -0
- data/lib/gush/workflow.rb +154 -0
- data/spec/Gushfile.rb +0 -0
- data/spec/lib/gush/client_spec.rb +125 -0
- data/spec/lib/gush/configuration_spec.rb +27 -0
- data/spec/lib/gush/job_spec.rb +114 -0
- data/spec/lib/gush/logger_builder_spec.rb +25 -0
- data/spec/lib/gush/null_logger_spec.rb +15 -0
- data/spec/lib/gush/worker_spec.rb +96 -0
- data/spec/lib/gush/workflow_spec.rb +246 -0
- data/spec/lib/gush_spec.rb +39 -0
- data/spec/redis.conf +2 -0
- data/spec/spec_helper.rb +79 -0
- metadata +256 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ee767bd97e343cac8bf395a65d2d19c63abd638b
|
4
|
+
data.tar.gz: d5a41126c1895e505871ae108613955466724829
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ce471a04c947ff17fc3864644223463da25ca52aa59e34cc2118bcb607526db2b394c79b6b708af23bc29a7ae4efb5647e5bd19a09e51d9df253e1b8442fac42
|
7
|
+
data.tar.gz: dda4e7862751f8c7a7cc625f7bdafaca54dc6718a96689346d010d4ae8f6b3350dff9cd07cf461ad369930eb90f75e0bf33242f685be85ed0482065e8f8f367d
|
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
workflows/
|
18
|
+
tmp
|
19
|
+
test.rb
|
20
|
+
/Gushfile.rb
|
21
|
+
dump.rdb
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Piotrek Okoński
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# Gush
|
2
|
+
|
3
|
+
Gush is a parallel workflow runner using only Redis as its message broker and Sidekiq for workers.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'gush'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install gush
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Your project should contain a file called `Gushfile.rb` which loads all the necessary workflows for Sidekiq to use.
|
22
|
+
|
23
|
+
Example:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require_relative './lib/your_project'
|
27
|
+
|
28
|
+
Dir[Rowlf.root.join("workflows/**/*.rb")].each do |file|
|
29
|
+
require file
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
### Defining workflows
|
34
|
+
|
35
|
+
The DSL for defining jobs consists of a single `run` method.
|
36
|
+
Here is a complete example of a workflow you can create:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# workflows/sample_workflow.rb
|
40
|
+
class SampleWorkflow < Gush::Workflow
|
41
|
+
def configure
|
42
|
+
run FetchJob1
|
43
|
+
run FetchJob2
|
44
|
+
|
45
|
+
run PersistJob1, after: FetchJob1
|
46
|
+
run PersistJob2, after: FetchJob2
|
47
|
+
|
48
|
+
run Normalize,
|
49
|
+
after: [PersistJob1, PersistJob2],
|
50
|
+
before: Index
|
51
|
+
|
52
|
+
run Index
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
**Hint:** For debugging purposes you can vizualize the graph using `viz` command:
|
58
|
+
|
59
|
+
```
|
60
|
+
bundle exec gush viz SampleWorkflow
|
61
|
+
```
|
62
|
+
|
63
|
+
For the Workflow above, the graph will look like this:
|
64
|
+
|
65
|
+

|
66
|
+
### Defining jobs
|
67
|
+
|
68
|
+
Jobs are classes inheriting from `Gush::Job`:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
#workflows/sample/fetch_job1.rb
|
72
|
+
class FetchJob1 < Gush::Job
|
73
|
+
def work
|
74
|
+
# do some fetching from remote APIs
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
### Running
|
80
|
+
|
81
|
+
#### 1. Register workflow
|
82
|
+
|
83
|
+
After you define your workflows and jobs, all you have to do is register them:
|
84
|
+
|
85
|
+
```
|
86
|
+
bundle exec gush create SampleWorkflow
|
87
|
+
```
|
88
|
+
|
89
|
+
the command will return a unique workflow id you will use in later commands.
|
90
|
+
|
91
|
+
#### 2. Run workers
|
92
|
+
|
93
|
+
This will start Sidekiq workers responsible for processing jobs
|
94
|
+
|
95
|
+
```
|
96
|
+
bundle exec gush workers
|
97
|
+
```
|
98
|
+
|
99
|
+
#### 3. Start the workflow
|
100
|
+
|
101
|
+
Use your workflow_id returned by `create` command.
|
102
|
+
|
103
|
+
```
|
104
|
+
bundle gush start <workflow_id>
|
105
|
+
```
|
106
|
+
|
107
|
+
### 5. Check the status
|
108
|
+
|
109
|
+
- of a specific workflow:
|
110
|
+
|
111
|
+
```
|
112
|
+
bundle gush show <workflow_id>
|
113
|
+
```
|
114
|
+
|
115
|
+
- of all created workflows:
|
116
|
+
|
117
|
+
```
|
118
|
+
bundle gush list
|
119
|
+
```
|
120
|
+
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
1. Fork it ( http://github.com/lonelyplanet/gush/fork )
|
125
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
126
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
127
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
128
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/gush
ADDED
data/gush.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gush/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gush"
|
8
|
+
spec.version = Gush::VERSION
|
9
|
+
spec.authors = ["Piotrek Okoński"]
|
10
|
+
spec.email = ["piotrek@okonski.org"]
|
11
|
+
spec.summary = "Fast and distributed workflow runner using only Sidekiq and Redis"
|
12
|
+
spec.homepage = "https://github.com/pokonski/gush"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = "gush"
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "sidekiq", "~> 3.2.2"
|
21
|
+
spec.add_dependency "yajl-ruby"
|
22
|
+
spec.add_dependency "redis", "~> 3.0.0"
|
23
|
+
spec.add_dependency "hiredis", "~> 0.5.2"
|
24
|
+
spec.add_dependency "ruby-graphviz"
|
25
|
+
spec.add_dependency "terminal-table"
|
26
|
+
spec.add_dependency "colorize"
|
27
|
+
spec.add_dependency "thor"
|
28
|
+
spec.add_dependency "launchy"
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
30
|
+
spec.add_development_dependency "rake"
|
31
|
+
spec.add_development_dependency "rspec", '~> 3.0.0'
|
32
|
+
end
|
data/lib/gush.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
require "graphviz"
|
4
|
+
require "hiredis"
|
5
|
+
require "pathname"
|
6
|
+
require "redis"
|
7
|
+
require "securerandom"
|
8
|
+
require "sidekiq"
|
9
|
+
|
10
|
+
require "gush/cli"
|
11
|
+
require "gush/client"
|
12
|
+
require "gush/configuration"
|
13
|
+
require "gush/errors"
|
14
|
+
require "gush/job"
|
15
|
+
require "gush/logger_builder"
|
16
|
+
require "gush/metadata"
|
17
|
+
require "gush/null_logger"
|
18
|
+
require "gush/version"
|
19
|
+
require "gush/worker"
|
20
|
+
require "gush/workflow"
|
21
|
+
|
22
|
+
module Gush
|
23
|
+
def self.gushfile
|
24
|
+
configuration.gushfile
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.root
|
28
|
+
Pathname.new(__FILE__).parent.parent
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.configuration
|
32
|
+
@configuration ||= Configuration.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.configure
|
36
|
+
yield configuration
|
37
|
+
reconfigure_sidekiq_server
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.reconfigure_sidekiq_server
|
41
|
+
Sidekiq.configure_server do |config|
|
42
|
+
config.redis = { url: configuration.redis_url, queue: configuration.namespace}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Gush.reconfigure_sidekiq_server
|
data/lib/gush/cli.rb
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
require 'colorize'
|
3
|
+
require 'thor'
|
4
|
+
require 'launchy'
|
5
|
+
require 'sidekiq'
|
6
|
+
require 'sidekiq/api'
|
7
|
+
|
8
|
+
module Gush
|
9
|
+
class CLI < Thor
|
10
|
+
class_option :gushfile, desc: "configuration file to use", aliases: "-f"
|
11
|
+
class_option :concurrency, desc: "concurrency setting for Sidekiq", aliases: "-c"
|
12
|
+
class_option :redis, desc: "Redis URL to use", aliases: "-r"
|
13
|
+
class_option :namespace, desc: "namespace to run jobs in", aliases: "-n"
|
14
|
+
class_option :env, desc: "Sidekiq environment", aliases: "-e"
|
15
|
+
|
16
|
+
def initialize(*)
|
17
|
+
super
|
18
|
+
Gush.configure do |config|
|
19
|
+
config.gushfile = options.fetch("gushfile", config.gushfile)
|
20
|
+
config.concurrency = options.fetch("concurrency", config.concurrency)
|
21
|
+
config.redis_url = options.fetch("redis", config.redis_url)
|
22
|
+
config.namespace = options.fetch("namespace", config.namespace)
|
23
|
+
config.environment = options.fetch("environment", config.environment)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "create [WorkflowClass]", "Registers new workflow"
|
28
|
+
def create(name)
|
29
|
+
workflow = client.create_workflow(name)
|
30
|
+
puts "Workflow created with id: #{workflow.id}"
|
31
|
+
puts "Start it with command: gush start #{workflow.id}"
|
32
|
+
rescue
|
33
|
+
puts "Workflow not found."
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "start [workflow_id]", "Starts Workflow with given ID"
|
37
|
+
def start(*args)
|
38
|
+
id = args.shift
|
39
|
+
client.start_workflow(id, args)
|
40
|
+
rescue WorkflowNotFound
|
41
|
+
puts "Workflow not found."
|
42
|
+
rescue DependencyLevelTooDeep
|
43
|
+
puts "Dependency level too deep. Perhaps you have a dependency cycle?"
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "create_and_start [WorkflowClass]", "Create and instantly start the new workflow"
|
47
|
+
def create_and_start(name, *args)
|
48
|
+
workflow = client.create_workflow(name)
|
49
|
+
client.start_workflow(workflow.id, args)
|
50
|
+
puts "Created and started workflow with id: #{workflow.id}"
|
51
|
+
rescue WorkflowNotFound
|
52
|
+
puts "Workflow not found."
|
53
|
+
rescue DependencyLevelTooDeep
|
54
|
+
puts "Dependency level too deep. Perhaps you have a dependency cycle?"
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "stop [workflow_id]", "Stops Workflow with given ID"
|
58
|
+
def stop(*args)
|
59
|
+
id = args.shift
|
60
|
+
client.stop_workflow(id)
|
61
|
+
rescue WorkflowNotFound
|
62
|
+
puts "Workflow not found."
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "clear", "Clears all jobs from Sidekiq queue"
|
66
|
+
def clear
|
67
|
+
Sidekiq::Queue.new(client.configuration.namespace).clear
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "show [workflow_id]", "Shows details about workflow with given ID"
|
71
|
+
option :skip_overview, type: :boolean
|
72
|
+
option :skip_jobs, type: :boolean
|
73
|
+
option :jobs, default: :all
|
74
|
+
def show(workflow_id)
|
75
|
+
workflow = client.find_workflow(workflow_id)
|
76
|
+
|
77
|
+
display_overview_for(workflow) unless options[:skip_overview]
|
78
|
+
|
79
|
+
display_jobs_list_for(workflow, options[:jobs]) unless options[:skip_jobs]
|
80
|
+
rescue WorkflowNotFound
|
81
|
+
puts "Workflow not found."
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "rm [workflow_id]", "Delete workflow with given ID"
|
85
|
+
def rm(workflow_id)
|
86
|
+
workflow = client.find_workflow(workflow_id)
|
87
|
+
client.destroy_workflow(workflow)
|
88
|
+
rescue WorkflowNotFound
|
89
|
+
puts "Workflow not found."
|
90
|
+
end
|
91
|
+
|
92
|
+
desc "list", "Lists all workflows with their statuses"
|
93
|
+
def list
|
94
|
+
workflows = client.all_workflows
|
95
|
+
rows = workflows.map do |workflow|
|
96
|
+
[workflow.id, workflow.class, {alignment: :center, value: status_for(workflow)}]
|
97
|
+
end
|
98
|
+
headers = [
|
99
|
+
{alignment: :center, value: 'id'},
|
100
|
+
{alignment: :center, value: 'name'},
|
101
|
+
{alignment: :center, value: 'status'}
|
102
|
+
]
|
103
|
+
puts Terminal::Table.new(headings: headers, rows: rows)
|
104
|
+
end
|
105
|
+
|
106
|
+
desc "workers", "Starts Sidekiq workers"
|
107
|
+
def workers
|
108
|
+
config = client.configuration
|
109
|
+
Kernel.exec "bundle exec sidekiq -r #{config.gushfile} -c #{config.concurrency} -q #{config.namespace} -e #{config.environment} -v"
|
110
|
+
end
|
111
|
+
|
112
|
+
desc "viz [WorkflowClass]", "Displays graph, visualising job dependencies"
|
113
|
+
def viz(name)
|
114
|
+
client
|
115
|
+
workflow = name.constantize.new("start")
|
116
|
+
GraphViz.new(:G, type: :digraph, dpi: 200, compound: true) do |g|
|
117
|
+
g[:compound] = true
|
118
|
+
g[:rankdir] = "LR"
|
119
|
+
g[:center] = true
|
120
|
+
g.node[:shape] = "ellipse"
|
121
|
+
g.node[:style] = "filled"
|
122
|
+
g.node[:color] = "#555555"
|
123
|
+
g.node[:fillcolor] = "white"
|
124
|
+
g.edge[:dir] = "forward"
|
125
|
+
g.edge[:penwidth] = 1
|
126
|
+
g.edge[:color] = "#555555"
|
127
|
+
start = g.start(shape: 'diamond', fillcolor: '#CFF09E')
|
128
|
+
end_node = g.end(shape: 'diamond', fillcolor: '#F56991')
|
129
|
+
|
130
|
+
|
131
|
+
workflow.nodes.each do |job|
|
132
|
+
name = job.class.to_s
|
133
|
+
g.add_nodes(name)
|
134
|
+
|
135
|
+
if job.incoming.empty?
|
136
|
+
g.add_edges(start, name)
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
if job.outgoing.empty?
|
141
|
+
g.add_edges(name, end_node)
|
142
|
+
else
|
143
|
+
job.outgoing.each do |out|
|
144
|
+
g.add_edges(name, out)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
g.output(png: Pathname.new(Dir.tmpdir).join("graph.png"))
|
150
|
+
end
|
151
|
+
|
152
|
+
Launchy.open(Pathname.new(Dir.tmpdir).join("graph.png").to_s)
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def client
|
158
|
+
@client ||= Client.new
|
159
|
+
end
|
160
|
+
|
161
|
+
def display_overview_for(workflow)
|
162
|
+
rows = []
|
163
|
+
columns = {
|
164
|
+
"id" => workflow.id,
|
165
|
+
"name" => workflow.class.to_s,
|
166
|
+
"jobs" => workflow.nodes.count,
|
167
|
+
"failed jobs" => workflow.nodes.count(&:failed?).to_s.red,
|
168
|
+
"succeeded jobs" => workflow.nodes.count(&:succeeded?).to_s.green,
|
169
|
+
"enqueued jobs" => workflow.nodes.count(&:enqueued?).to_s.yellow,
|
170
|
+
"running jobs" => workflow.nodes.count(&:running?).to_s.blue,
|
171
|
+
"remaining jobs" => workflow.nodes.count{|j| [j.finished, j.failed, j.enqueued].all? {|b| !b} },
|
172
|
+
"status" => status_for(workflow)
|
173
|
+
}
|
174
|
+
|
175
|
+
columns.each_pair do |name, value|
|
176
|
+
rows << [{alignment: :center, value: name}, value]
|
177
|
+
rows << :separator if name != "status"
|
178
|
+
end
|
179
|
+
|
180
|
+
puts Terminal::Table.new(rows: rows)
|
181
|
+
end
|
182
|
+
|
183
|
+
def status_for(workflow)
|
184
|
+
if workflow.failed?
|
185
|
+
status = "failed".light_red
|
186
|
+
status += "\n#{workflow.nodes.find(&:failed).name} failed"
|
187
|
+
elsif workflow.running?
|
188
|
+
status = "running".yellow
|
189
|
+
finished = workflow.nodes.count {|job| job.finished }
|
190
|
+
total = workflow.nodes.count
|
191
|
+
status += "\n#{finished}/#{total} [#{(finished*100)/total}%]"
|
192
|
+
elsif workflow.finished?
|
193
|
+
status = "done".green
|
194
|
+
elsif workflow.stopped?
|
195
|
+
status = "stopped".red
|
196
|
+
else
|
197
|
+
status = "pending".light_white
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def display_jobs_list_for(workflow, jobs)
|
202
|
+
puts "\nJobs list:\n"
|
203
|
+
|
204
|
+
jobs_by_type(workflow, jobs).each do |job|
|
205
|
+
name = job.name
|
206
|
+
puts case
|
207
|
+
when job.failed?
|
208
|
+
"[✗] #{name.red}"
|
209
|
+
when job.finished?
|
210
|
+
"[✓] #{name.green}"
|
211
|
+
when job.enqueued?
|
212
|
+
"[•] #{name.yellow}"
|
213
|
+
when job.running?
|
214
|
+
"[•] #{name.blue}"
|
215
|
+
else
|
216
|
+
"[ ] #{name}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def jobs_by_type(workflow, type)
|
222
|
+
jobs = workflow.nodes.sort_by do |job|
|
223
|
+
case
|
224
|
+
when job.failed?
|
225
|
+
0
|
226
|
+
when job.finished?
|
227
|
+
1
|
228
|
+
when job.enqueued?
|
229
|
+
2
|
230
|
+
when job.running?
|
231
|
+
3
|
232
|
+
else
|
233
|
+
4
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
jobs.select!{|j| j.public_send("#{type}?") } unless type == :all
|
238
|
+
jobs
|
239
|
+
end
|
240
|
+
|
241
|
+
def gushfile
|
242
|
+
Pathname.pwd.join(options[:gushfile])
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|