gush 2.0.2 → 3.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12fdb9a62d33353f827194c198c011ff42d7491669d0ab9717be0920a8066313
4
- data.tar.gz: 7f3d4ada23215e818f0cb801c92f4752e80b21b344f3b48d9ab4b83787c029ec
3
+ metadata.gz: e588202fe13ded7c99f192bda5bc33d3d6e8994deb3fb17f5a823d4882c4552f
4
+ data.tar.gz: 759593f344caf579cce496b1da9c64c9431d6f2cbb92cbcced181d49421fdefb
5
5
  SHA512:
6
- metadata.gz: bec4bcb3e251bdb1a2e184b6b62ae896fd40af4a0cd4bad2ec7ee4925ab356e36a3388d2cb78ed21582c0f6cfdd429c304f0591a5ee3760c73255d03a2b1d80e
7
- data.tar.gz: 67fba0b65b575449114233a4ff9cfaf1cb6ac3ff61633f94b7c595988165b3315fe7ae5d0744792315d9329795da0e42ae840789e3d4f72c84ef8956081c5fbd
6
+ metadata.gz: 5d6068f23d178beb5dbfaa9cf317ae086542dedf33aeab82f82c8cab5a6ea569d932b2dc0a787978629e892a39f25270827c55fa4a6b0a6bb349f9ab87fca1db
7
+ data.tar.gz: 69ddc27452586d4b188969ece9ab73aefd90377fd93503f3e83c9fd074722d808a09cc620f98953756d7c684bab808db14550c15bada3e93111815613e52b78c
@@ -0,0 +1,46 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ pull_request:
12
+ paths-ignore:
13
+ - 'README.md'
14
+ push:
15
+ paths-ignore:
16
+ - 'README.md'
17
+
18
+ jobs:
19
+ test:
20
+ services:
21
+ redis:
22
+ image: redis:alpine
23
+ ports: ["6379:6379"]
24
+ options: --entrypoint redis-server
25
+
26
+ runs-on: ubuntu-latest
27
+ strategy:
28
+ matrix:
29
+ rails_version: ['6.1.0', '7.0', '7.1.0']
30
+ ruby-version: ['3.0', '3.1', '3.2', '3.3']
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - name: Set up Ruby
34
+ uses: ruby/setup-ruby@v1
35
+ with:
36
+ ruby-version: ${{ matrix.ruby-version }}
37
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
38
+ env:
39
+ RAILS_VERSION: "${{ matrix.rails_version }}"
40
+ - name: Install Graphviz
41
+ run: sudo apt-get install graphviz
42
+ - name: Run tests
43
+ run: bundle exec rspec
44
+ env:
45
+ REDIS_URL: redis://localhost:6379/1
46
+ RAILS_VERSION: "${{ matrix.rails_version }}"
data/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 2.1.0
9
+
10
+ ### Added
11
+
12
+ - Allow RedisMutex’s locking duration and polling interval to be customizable, thanks to @thukim! [See pull request](https://github.com/chaps-io/gush/pull/74)
13
+ - Support for Rails 7.0 and Ruby 3.0-3.1, thanks to @joshRpowell and @kzkn!
14
+
8
15
  ## 2.0.1
9
16
 
10
17
  ### Fixed
@@ -13,31 +20,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
13
20
 
14
21
  ## 2.0.0
15
22
 
16
- ## Changed
23
+ ### Changed
17
24
 
18
25
  - *[BREAKING]* Store gush jobs on redis hash instead of plain keys - this improves performance when retrieving keys (Thanks to @Saicheg! [See pull request](https://github.com/chaps-io/gush/pull/56))
19
26
 
20
27
 
21
- ## Added
28
+ ### Added
22
29
 
23
30
  - Allow setting queue for each job via `:queue` option in `run` method (Thanks to @devilankur18! [See pull request](https://github.com/chaps-io/gush/pull/58))
24
31
 
25
32
 
26
33
  ## 1.1.1 - 2018-06-09
27
34
 
28
- ## Changed
35
+ ### Changed
29
36
 
30
37
  - Relax dependency on ActiveSupport to work with 4.2 up to 5.X (Thanks to @iacobus! [See pull request](https://github.com/chaps-io/gush/pull/54))
31
38
 
32
39
 
33
40
  ## 1.1.0 - 2018-02-05
34
41
 
35
- ## Added
42
+ ### Added
36
43
 
37
44
  - Added ability to specify TTL for Redis keys and manually expire whole workflows (Thanks to @dmitrypol! [See pull request](https://github.com/chaps-io/gush/pull/48))
38
45
  - Loosened dependency on redis-rb library to >= 3.2 and < 5.0 (Thanks to @mofumofu3n! [See pull request](https://github.com/chaps-io/gush/pull/52))
39
46
 
40
- ## Fixed
47
+ ### Fixed
41
48
 
42
49
  - Improved performance of (de)serializing workflows by not storing job array inside workflow JSON and other smaller improvements ([See pull request](https://github.com/chaps-io/gush/pull/53))
43
50
 
data/Gemfile CHANGED
@@ -1,6 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
+ rails_version = ENV['RAILS_VERSION'] || '< 7.0'
5
+ rails_version = "~> #{rails_version}" if rails_version =~ /^\d/
6
+ gem 'activejob', rails_version
7
+
4
8
  platforms :mri, :ruby do
5
9
  gem 'yajl-ruby'
6
10
  end
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # Gush [![Build Status](https://travis-ci.org/chaps-io/gush.svg?branch=master)](https://travis-ci.org/chaps-io/gush)
1
+ # Gush
2
+
3
+ ![Gem Version](https://img.shields.io/gem/v/gush)
4
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/chaps-io/gush/ruby.yml)
5
+
2
6
 
3
7
  Gush is a parallel workflow runner using only Redis as storage and [ActiveJob](http://guides.rubyonrails.org/v4.2/active_job_basics.html#introduction) for scheduling and executing jobs.
4
8
 
@@ -8,14 +12,14 @@ Gush relies on directed acyclic graphs to store dependencies, see [Parallelizing
8
12
 
9
13
  ## **WARNING - version notice**
10
14
 
11
- This README is about the `1.0.0` version, which has breaking changes compared to < 1.0.0 versions. [See here for 0.4.1 documentation](https://github.com/chaps-io/gush/blob/349c5aff0332fd14b1cb517115c26d415aa24841/README.md).
15
+ This README is about the latest `master` code, which might differ from what is released on RubyGems. See tags to browse previous READMEs.
12
16
 
13
17
  ## Installation
14
18
 
15
19
  ### 1. Add `gush` to Gemfile
16
20
 
17
21
  ```ruby
18
- gem 'gush', '~> 1.0.0'
22
+ gem 'gush', '~> 3.0'
19
23
  ```
20
24
 
21
25
  ### 2. Create `Gushfile`
@@ -74,7 +78,17 @@ end
74
78
 
75
79
  and this is how the graph will look like:
76
80
 
77
- ![SampleWorkflow](https://i.imgur.com/DFh6j51.png)
81
+ ```mermaid
82
+ graph TD
83
+ A{Start} --> B[FetchJob1]
84
+ A --> C[FetchJob2]
85
+ B --> D[PersistJob1]
86
+ C --> E[PersistJob2]
87
+ D --> F[NormalizeJob]
88
+ E --> F
89
+ F --> G[IndexJob]
90
+ G --> H{Finish}
91
+ ```
78
92
 
79
93
 
80
94
  ## Defining workflows
@@ -211,7 +225,7 @@ For example, in case of Sidekiq this would be:
211
225
  bundle exec sidekiq -q gush
212
226
  ```
213
227
 
214
- **[Click here to see backends section in official ActiveJob documentation about configuring backends](http://guides.rubyonrails.org/v4.2/active_job_basics.html#backends)**
228
+ **[Click here to see backends section in official ActiveJob documentation about configuring backends](http://guides.rubyonrails.org/active_job_basics.html#backends)**
215
229
 
216
230
  **Hint**: gush uses `gush` queue name by default. Keep that in mind, because some backends (like Sidekiq) will only run jobs from explicitly stated queues.
217
231
 
@@ -274,7 +288,7 @@ class EncodeVideo < Gush::Job
274
288
  end
275
289
  ```
276
290
 
277
- `payloads` is an array containing outputs from all ancestor jobs. So for our `EncodeVide` job from above, the array will look like:
291
+ `payloads` is an array containing outputs from all ancestor jobs. So for our `EncodeVideo` job from above, the array will look like:
278
292
 
279
293
 
280
294
  ```ruby
@@ -319,7 +333,55 @@ flow = NotifyWorkflow.create([54, 21, 24, 154, 65]) # 5 user ids as an argument
319
333
 
320
334
  it will generate a workflow with 5 `NotificationJob`s and one `AdminNotificationJob` which will depend on all of them:
321
335
 
322
- ![DynamicWorkflow](https://i.imgur.com/HOI3fjc.png)
336
+
337
+ ```mermaid
338
+ graph TD
339
+ A{Start} --> B[NotificationJob]
340
+ A --> C[NotificationJob]
341
+ A --> D[NotificationJob]
342
+ A --> E[NotificationJob]
343
+ A --> F[NotificationJob]
344
+ B --> G[AdminNotificationJob]
345
+ C --> G
346
+ D --> G
347
+ E --> G
348
+ F --> G
349
+ G --> H{Finish}
350
+ ```
351
+
352
+ ### Dynamic queue for jobs
353
+
354
+ There might be a case you want to configure different jobs in the workflow using different queues. Based on the above the example, we want to config `AdminNotificationJob` to use queue `admin` and `NotificationJob` use queue `user`.
355
+
356
+ ```ruby
357
+
358
+ class NotifyWorkflow < Gush::Workflow
359
+ def configure(user_ids)
360
+ notification_jobs = user_ids.map do |user_id|
361
+ run NotificationJob, params: {user_id: user_id}, queue: 'user'
362
+ end
363
+
364
+ run AdminNotificationJob, after: notification_jobs, queue: 'admin'
365
+ end
366
+ end
367
+ ```
368
+
369
+ ### Dynamic waitable time for jobs
370
+
371
+ There might be a case you want to configure a job to be executed after a time. Based on above example, we want to configure `AdminNotificationJob` to be executed after 5 seconds.
372
+
373
+ ```ruby
374
+
375
+ class NotifyWorkflow < Gush::Workflow
376
+ def configure(user_ids)
377
+ notification_jobs = user_ids.map do |user_id|
378
+ run NotificationJob, params: {user_id: user_id}, queue: 'user'
379
+ end
380
+
381
+ run AdminNotificationJob, after: notification_jobs, queue: 'admin', wait: 5.seconds
382
+ end
383
+ end
384
+ ```
323
385
 
324
386
  ## Command line interface (CLI)
325
387
 
@@ -346,9 +408,23 @@ This requires that you have imagemagick installed on your computer:
346
408
  bundle exec gush viz <NameOfTheWorkflow>
347
409
  ```
348
410
 
411
+ ### Customizing locking options
412
+
413
+ In order to prevent getting the RedisMutex::LockError error when having a large number of jobs, you can customize these 2 fields `locking_duration` and `polling_interval` as below
414
+
415
+ ```ruby
416
+ # config/initializers/gush.rb
417
+ Gush.configure do |config|
418
+ config.redis_url = "redis://localhost:6379"
419
+ config.concurrency = 5
420
+ config.locking_duration = 2 # how long you want to wait for the lock to be released, in seconds
421
+ config.polling_interval = 0.3 # how long the polling interval should be, in seconds
422
+ end
423
+ ```
424
+
349
425
  ### Cleaning up afterwards
350
426
 
351
- Running `NotifyWorkflow.create` inserts multiple keys into Redis every time it is ran. This data might be useful for analysis but at a certain point it can be purged via Redis TTL. By default gush and Redis will keep keys forever. To configure expiration you need to 2 things. Create initializer (specify config.ttl in seconds, be different per environment).
427
+ Running `NotifyWorkflow.create` inserts multiple keys into Redis every time it is ran. This data might be useful for analysis but at a certain point it can be purged via Redis TTL. By default gush and Redis will keep keys forever. To configure expiration you need to 2 things. Create initializer (specify config.ttl in seconds, be different per environment).
352
428
 
353
429
  ```ruby
354
430
  # config/initializers/gush.rb
@@ -359,7 +435,7 @@ Gush.configure do |config|
359
435
  end
360
436
  ```
361
437
 
362
- And you need to call `flow.expire!` (optionally passing custom TTL value overriding `config.ttl`). This gives you control whether to expire data for specific workflow. Best NOT to set TTL to be too short (like minutes) but about a week in length. And you can run `Client.expire_workflow` and `Client.expire_job` passing appropriate IDs and TTL (pass -1 to NOT expire) values.
438
+ And you need to call `flow.expire!` (optionally passing custom TTL value overriding `config.ttl`). This gives you control whether to expire data for specific workflow. Best NOT to set TTL to be too short (like minutes) but about a week in length. And you can run `Client.expire_workflow` and `Client.expire_job` passing appropriate IDs and TTL (pass -1 to NOT expire) values.
363
439
 
364
440
  ### Avoid overlapping workflows
365
441
 
data/bin/gush CHANGED
@@ -12,7 +12,7 @@ require 'gush'
12
12
  begin
13
13
  Gush::CLI.start(ARGV)
14
14
  rescue Gush::WorkflowNotFound
15
- puts "Workflow not found".red
15
+ puts Paint["Workflow not found", :red]
16
16
  rescue Gush::DependencyLevelTooDeep
17
- puts "Dependency level too deep. Perhaps you have a dependency cycle?".red
17
+ puts Paint["Dependency level too deep. Perhaps you have a dependency cycle?", :red]
18
18
  end
data/gush.gemspec CHANGED
@@ -2,11 +2,13 @@
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
+ require_relative 'lib/gush/version'
6
+
5
7
  Gem::Specification.new do |spec|
6
8
  spec.name = "gush"
7
- spec.version = "2.0.2"
8
- spec.authors = ["Piotrek Okoński"]
9
- spec.email = ["piotrek@okonski.org"]
9
+ spec.version = Gush::VERSION
10
+ spec.authors = ["Piotrek Okoński", "Michał Krzyżanowski"]
11
+ spec.email = ["piotrek@okonski.org", "michal.krzyzanowski+github@gmail.com"]
10
12
  spec.summary = "Fast and distributed workflow runner based on ActiveJob and Redis"
11
13
  spec.description = "Gush is a parallel workflow runner using Redis as storage and ActiveJob for executing jobs."
12
14
  spec.homepage = "https://github.com/chaps-io/gush"
@@ -16,21 +18,21 @@ Gem::Specification.new do |spec|
16
18
  spec.executables = "gush"
17
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
20
  spec.require_paths = ["lib"]
21
+ spec.required_ruby_version = '>= 3.0.0'
19
22
 
20
- spec.add_dependency "activejob", ">= 4.2.7", "< 7.0"
23
+ spec.add_dependency "activejob", ">= 6.1.0", "< 7.2"
21
24
  spec.add_dependency "concurrent-ruby", "~> 1.0"
22
25
  spec.add_dependency "multi_json", "~> 1.11"
23
- spec.add_dependency "redis", ">= 3.2", "< 5"
26
+ spec.add_dependency "redis", ">= 3.2", "< 6"
24
27
  spec.add_dependency "redis-mutex", "~> 4.0.1"
25
28
  spec.add_dependency "hiredis", "~> 0.6"
26
- spec.add_dependency "ruby-graphviz", "~> 1.2"
27
- spec.add_dependency "terminal-table", "~> 1.4"
28
- spec.add_dependency "colorize", "~> 0.7"
29
- spec.add_dependency "thor", "~> 0.19"
29
+ spec.add_dependency "graphviz", "~> 1.2"
30
+ spec.add_dependency "terminal-table", ">= 1.4", "< 3.1"
31
+ spec.add_dependency "paint", "~> 2.2"
32
+ spec.add_dependency "thor", ">= 0.19", "< 1.3"
30
33
  spec.add_dependency "launchy", "~> 2.4"
31
34
  spec.add_development_dependency "bundler"
32
- spec.add_development_dependency "rake", "~> 10.4"
35
+ spec.add_development_dependency "rake", "~> 12"
33
36
  spec.add_development_dependency "rspec", '~> 3.0'
34
37
  spec.add_development_dependency "pry", '~> 0.10'
35
- spec.add_development_dependency 'fakeredis', '~> 0.5'
36
38
  end
@@ -17,11 +17,11 @@ module Gush
17
17
  elsif workflow.running?
18
18
  running_status
19
19
  elsif workflow.finished?
20
- "done".green
20
+ Paint["done", :green]
21
21
  elsif workflow.stopped?
22
- "stopped".red
22
+ Paint["stopped", :red]
23
23
  else
24
- "ready to start".blue
24
+ Paint["ready to start", :blue]
25
25
  end
26
26
  end
27
27
 
@@ -48,10 +48,10 @@ module Gush
48
48
  "ID" => workflow.id,
49
49
  "Name" => workflow.class.to_s,
50
50
  "Jobs" => workflow.jobs.count,
51
- "Failed jobs" => failed_jobs_count.red,
52
- "Succeeded jobs" => succeeded_jobs_count.green,
53
- "Enqueued jobs" => enqueued_jobs_count.yellow,
54
- "Running jobs" => running_jobs_count.blue,
51
+ "Failed jobs" => Paint[failed_jobs_count, :red],
52
+ "Succeeded jobs" => Paint[succeeded_jobs_count, :green],
53
+ "Enqueued jobs" => Paint[enqueued_jobs_count, :yellow],
54
+ "Running jobs" => Paint[running_jobs_count, :blue],
55
55
  "Remaining jobs" => remaining_jobs_count,
56
56
  "Started at" => started_at,
57
57
  "Status" => status
@@ -60,7 +60,7 @@ module Gush
60
60
 
61
61
  def running_status
62
62
  finished = succeeded_jobs_count.to_i
63
- status = "running".yellow
63
+ status = Paint["running", :yellow]
64
64
  status += "\n#{finished}/#{total_jobs_count} [#{(finished*100)/total_jobs_count}%]"
65
65
  end
66
66
 
@@ -69,7 +69,7 @@ module Gush
69
69
  end
70
70
 
71
71
  def failed_status
72
- status = "failed".light_red
72
+ status = Paint["failed", :red]
73
73
  status += "\n#{failed_job} failed"
74
74
  end
75
75
 
@@ -77,13 +77,13 @@ module Gush
77
77
  name = job.name
78
78
  case
79
79
  when job.failed?
80
- "[✗] #{name.red} \n"
80
+ "[✗] #{Paint[name, :red]} \n"
81
81
  when job.finished?
82
- "[✓] #{name.green} \n"
82
+ "[✓] #{Paint[name, :green]} \n"
83
83
  when job.enqueued?
84
- "[•] #{name.yellow} \n"
84
+ "[•] #{Paint[name, :yellow]} \n"
85
85
  when job.running?
86
- "[•] #{name.blue} \n"
86
+ "[•] #{Paint[name, :blue]} \n"
87
87
  else
88
88
  "[ ] #{name} \n"
89
89
  end
data/lib/gush/cli.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'terminal-table'
2
- require 'colorize'
4
+ require 'paint'
3
5
  require 'thor'
4
6
  require 'launchy'
5
7
 
@@ -12,43 +14,45 @@ module Gush
12
14
  def initialize(*)
13
15
  super
14
16
  Gush.configure do |config|
15
- config.gushfile = options.fetch("gushfile", config.gushfile)
16
- config.concurrency = options.fetch("concurrency", config.concurrency)
17
- config.redis_url = options.fetch("redis", config.redis_url)
18
- config.namespace = options.fetch("namespace", config.namespace)
19
- config.ttl = options.fetch("ttl", config.ttl)
17
+ config.gushfile = options.fetch("gushfile", config.gushfile)
18
+ config.concurrency = options.fetch("concurrency", config.concurrency)
19
+ config.redis_url = options.fetch("redis", config.redis_url)
20
+ config.namespace = options.fetch("namespace", config.namespace)
21
+ config.ttl = options.fetch("ttl", config.ttl)
22
+ config.locking_duration = options.fetch("locking_duration", config.locking_duration)
23
+ config.polling_interval = options.fetch("polling_interval", config.polling_interval)
20
24
  end
21
25
  load_gushfile
22
26
  end
23
27
 
24
- desc "create [WorkflowClass]", "Registers new workflow"
28
+ desc "create WORKFLOW_CLASS", "Registers new workflow"
25
29
  def create(name)
26
30
  workflow = client.create_workflow(name)
27
31
  puts "Workflow created with id: #{workflow.id}"
28
32
  puts "Start it with command: gush start #{workflow.id}"
29
33
  end
30
34
 
31
- desc "start [workflow_id]", "Starts Workflow with given ID"
35
+ desc "start WORKFLOW_ID [ARG ...]", "Starts Workflow with given ID"
32
36
  def start(*args)
33
37
  id = args.shift
34
38
  workflow = client.find_workflow(id)
35
39
  client.start_workflow(workflow, args)
36
40
  end
37
41
 
38
- desc "create_and_start [WorkflowClass]", "Create and instantly start the new workflow"
42
+ desc "create_and_start WORKFLOW_CLASS [ARG ...]", "Create and instantly start the new workflow"
39
43
  def create_and_start(name, *args)
40
44
  workflow = client.create_workflow(name)
41
45
  client.start_workflow(workflow.id, args)
42
46
  puts "Created and started workflow with id: #{workflow.id}"
43
47
  end
44
48
 
45
- desc "stop [workflow_id]", "Stops Workflow with given ID"
49
+ desc "stop WORKFLOW_ID", "Stops Workflow with given ID"
46
50
  def stop(*args)
47
51
  id = args.shift
48
52
  client.stop_workflow(id)
49
53
  end
50
54
 
51
- desc "show [workflow_id]", "Shows details about workflow with given ID"
55
+ desc "show WORKFLOW_ID", "Shows details about workflow with given ID"
52
56
  option :skip_overview, type: :boolean
53
57
  option :skip_jobs, type: :boolean
54
58
  option :jobs, default: :all
@@ -60,7 +64,7 @@ module Gush
60
64
  display_jobs_list_for(workflow, options[:jobs]) unless options[:skip_jobs]
61
65
  end
62
66
 
63
- desc "rm [workflow_id]", "Delete workflow with given ID"
67
+ desc "rm WORKFLOW_ID", "Delete workflow with given ID"
64
68
  def rm(workflow_id)
65
69
  workflow = client.find_workflow(workflow_id)
66
70
  client.destroy_workflow(workflow)
@@ -81,13 +85,39 @@ module Gush
81
85
  puts Terminal::Table.new(headings: headers, rows: rows)
82
86
  end
83
87
 
84
- desc "viz [WorkflowClass]", "Displays graph, visualising job dependencies"
85
- def viz(name)
88
+ desc "viz {WORKFLOW_CLASS|WORKFLOW_ID}", "Displays graph, visualising job dependencies"
89
+ option :filename, type: :string, default: nil
90
+ option :open, type: :boolean, default: nil
91
+ def viz(class_or_id)
86
92
  client
87
- workflow = name.constantize.new
88
- graph = Graph.new(workflow)
93
+
94
+ begin
95
+ workflow = client.find_workflow(class_or_id)
96
+ rescue WorkflowNotFound
97
+ workflow = nil
98
+ end
99
+
100
+ unless workflow
101
+ begin
102
+ workflow = class_or_id.constantize.new
103
+ rescue NameError => e
104
+ STDERR.puts Paint["'#{class_or_id}' is not a valid workflow class or id", :red]
105
+ exit 1
106
+ end
107
+ end
108
+
109
+ opts = {}
110
+
111
+ if options[:filename]
112
+ opts[:filename], opts[:path] = File.split(options[:filename])
113
+ end
114
+
115
+ graph = Graph.new(workflow, **opts)
89
116
  graph.viz
90
- Launchy.open graph.path
117
+
118
+ if (options[:open].nil? && !options[:filename]) || options[:open]
119
+ Launchy.open Pathname.new(graph.path).realpath.to_s
120
+ end
91
121
  end
92
122
 
93
123
  private
@@ -118,13 +148,14 @@ module Gush
118
148
 
119
149
  def load_gushfile
120
150
  file = client.configuration.gushfile
121
- if !gushfile.exist?
122
- raise Thor::Error, "#{file} not found, please add it to your project".colorize(:red)
151
+
152
+ unless gushfile.exist?
153
+ raise Thor::Error, Paint["#{file} not found, please add it to your project", :red]
123
154
  end
124
155
 
125
156
  load file.to_s
126
157
  rescue LoadError
127
- raise Thor::Error, "failed to require #{file}".colorize(:red)
158
+ raise Thor::Error, Paint["failed to require #{file}", :red]
128
159
  end
129
160
  end
130
161
  end
data/lib/gush/client.rb CHANGED
@@ -72,7 +72,7 @@ module Gush
72
72
  id = nil
73
73
  loop do
74
74
  id = SecureRandom.uuid
75
- available = !redis.exists("gush.workflow.#{id}")
75
+ available = !redis.exists?("gush.workflow.#{id}")
76
76
 
77
77
  break if available
78
78
  end
@@ -156,8 +156,13 @@ module Gush
156
156
  job.enqueue!
157
157
  persist_job(workflow_id, job)
158
158
  queue = job.queue || configuration.namespace
159
-
160
- Gush::Worker.set(queue: queue).perform_later(*[workflow_id, job.name])
159
+ wait = job.wait
160
+
161
+ if wait.present?
162
+ Gush::Worker.set(queue: queue, wait: wait).perform_later(*[workflow_id, job.name])
163
+ else
164
+ Gush::Worker.set(queue: queue).perform_later(*[workflow_id, job.name])
165
+ end
161
166
  end
162
167
 
163
168
  private
@@ -1,17 +1,19 @@
1
1
  module Gush
2
2
  class Configuration
3
- attr_accessor :concurrency, :namespace, :redis_url, :ttl
3
+ attr_accessor :concurrency, :namespace, :redis_url, :ttl, :locking_duration, :polling_interval
4
4
 
5
5
  def self.from_json(json)
6
6
  new(Gush::JSON.decode(json, symbolize_keys: true))
7
7
  end
8
8
 
9
9
  def initialize(hash = {})
10
- self.concurrency = hash.fetch(:concurrency, 5)
11
- self.namespace = hash.fetch(:namespace, 'gush')
12
- self.redis_url = hash.fetch(:redis_url, 'redis://localhost:6379')
13
- self.gushfile = hash.fetch(:gushfile, 'Gushfile')
14
- self.ttl = hash.fetch(:ttl, -1)
10
+ self.concurrency = hash.fetch(:concurrency, 5)
11
+ self.namespace = hash.fetch(:namespace, 'gush')
12
+ self.redis_url = hash.fetch(:redis_url, 'redis://localhost:6379')
13
+ self.gushfile = hash.fetch(:gushfile, 'Gushfile')
14
+ self.ttl = hash.fetch(:ttl, -1)
15
+ self.locking_duration = hash.fetch(:locking_duration, 2) # how long you want to wait for the lock to be released, in seconds
16
+ self.polling_interval = hash.fetch(:polling_internal, 0.3) # how long the polling interval should be, in seconds
15
17
  end
16
18
 
17
19
  def gushfile=(path)
@@ -24,10 +26,12 @@ module Gush
24
26
 
25
27
  def to_hash
26
28
  {
27
- concurrency: concurrency,
28
- namespace: namespace,
29
- redis_url: redis_url,
30
- ttl: ttl
29
+ concurrency: concurrency,
30
+ namespace: namespace,
31
+ redis_url: redis_url,
32
+ ttl: ttl,
33
+ locking_duration: locking_duration,
34
+ polling_interval: polling_interval
31
35
  }
32
36
  end
33
37
 
data/lib/gush/graph.rb CHANGED
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tmpdir'
4
+
1
5
  module Gush
2
6
  class Graph
3
- attr_reader :workflow, :filename, :path, :start, :end_node
7
+ attr_reader :workflow, :filename, :path, :start_node, :end_node
4
8
 
5
9
  def initialize(workflow, options = {})
6
10
  @workflow = workflow
@@ -9,19 +13,26 @@ module Gush
9
13
  end
10
14
 
11
15
  def viz
12
- GraphViz.new(:G, graph_options) do |graph|
13
- set_node_options!(graph)
14
- set_edge_options!(graph)
16
+ @graph = Graphviz::Graph.new(**graph_options)
17
+ @start_node = add_node('start', shape: 'diamond', fillcolor: '#CFF09E')
18
+ @end_node = add_node('end', shape: 'diamond', fillcolor: '#F56991')
15
19
 
16
- @start = graph.start(shape: 'diamond', fillcolor: '#CFF09E')
17
- @end_node = graph.end(shape: 'diamond', fillcolor: '#F56991')
18
-
19
- workflow.jobs.each do |job|
20
- add_job(graph, job)
21
- end
20
+ # First, create nodes for all jobs
21
+ @job_name_to_node_map = {}
22
+ workflow.jobs.each do |job|
23
+ add_job_node(job)
24
+ end
22
25
 
23
- graph.output(png: path)
26
+ # Next, link up the jobs with edges
27
+ workflow.jobs.each do |job|
28
+ link_job_edges(job)
24
29
  end
30
+
31
+ format = 'png'
32
+ file_format = path.split('.')[-1]
33
+ format = file_format if file_format.length == 3
34
+
35
+ Graphviz::output(@graph, path: path, format: format)
25
36
  end
26
37
 
27
38
  def path
@@ -29,43 +40,43 @@ module Gush
29
40
  end
30
41
 
31
42
  private
32
- def add_job(graph, job)
33
- name = job.class.to_s
34
- graph.add_nodes(job.name, label: name)
43
+
44
+ def add_node(name, **specific_options)
45
+ @graph.add_node(name, **node_options.merge(specific_options))
46
+ end
47
+
48
+ def add_job_node(job)
49
+ @job_name_to_node_map[job.name] = add_node(job.name, label: node_label_for_job(job))
50
+ end
51
+
52
+ def link_job_edges(job)
53
+ job_node = @job_name_to_node_map[job.name]
35
54
 
36
55
  if job.incoming.empty?
37
- graph.add_edges(start, job.name)
56
+ @start_node.connect(job_node, **edge_options)
38
57
  end
39
58
 
40
59
  if job.outgoing.empty?
41
- graph.add_edges(job.name, end_node)
60
+ job_node.connect(@end_node, **edge_options)
42
61
  else
43
62
  job.outgoing.each do |id|
44
63
  outgoing_job = workflow.find_job(id)
45
- graph.add_edges(job.name, outgoing_job.name)
64
+ job_node.connect(@job_name_to_node_map[outgoing_job.name], **edge_options)
46
65
  end
47
66
  end
48
67
  end
49
68
 
50
- def set_node_options!(graph)
51
- node_options.each do |key, value|
52
- graph.node[key] = value
53
- end
54
- end
55
-
56
- def set_edge_options!(graph)
57
- edge_options.each do |key, value|
58
- graph.edge[key] = value
59
- end
69
+ def node_label_for_job(job)
70
+ job.class.to_s
60
71
  end
61
72
 
62
73
  def graph_options
63
74
  {
64
- type: :digraph,
65
- dpi: 200,
66
- compound: true,
67
- rankdir: "LR",
68
- center: true
75
+ dpi: 200,
76
+ compound: true,
77
+ rankdir: "LR",
78
+ center: true,
79
+ format: 'png'
69
80
  }
70
81
  end
71
82
 
data/lib/gush/job.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  module Gush
2
2
  class Job
3
3
  attr_accessor :workflow_id, :incoming, :outgoing, :params,
4
- :finished_at, :failed_at, :started_at, :enqueued_at, :payloads, :klass, :queue
4
+ :finished_at, :failed_at, :started_at, :enqueued_at, :payloads,
5
+ :klass, :queue, :wait
5
6
  attr_reader :id, :klass, :output_payload, :params
6
7
 
7
8
  def initialize(opts = {})
@@ -126,6 +127,7 @@ module Gush
126
127
  @output_payload = opts[:output_payload]
127
128
  @workflow_id = opts[:workflow_id]
128
129
  @queue = opts[:queue]
130
+ @wait = opts[:wait]
129
131
  end
130
132
  end
131
133
  end
@@ -0,0 +1,3 @@
1
+ module Gush
2
+ VERSION = '3.0.0'
3
+ end
data/lib/gush/worker.rb CHANGED
@@ -30,12 +30,16 @@ module Gush
30
30
 
31
31
  private
32
32
 
33
- attr_reader :client, :workflow_id, :job
33
+ attr_reader :client, :workflow_id, :job, :configuration
34
34
 
35
35
  def client
36
36
  @client ||= Gush::Client.new(Gush.configuration)
37
37
  end
38
38
 
39
+ def configuration
40
+ @configuration ||= client.configuration
41
+ end
42
+
39
43
  def setup_job(workflow_id, job_id)
40
44
  @workflow_id = workflow_id
41
45
  @job ||= client.find_job(workflow_id, job_id)
@@ -73,7 +77,11 @@ module Gush
73
77
 
74
78
  def enqueue_outgoing_jobs
75
79
  job.outgoing.each do |job_name|
76
- RedisMutex.with_lock("gush_enqueue_outgoing_jobs_#{workflow_id}-#{job_name}", sleep: 0.3, block: 2) do
80
+ RedisMutex.with_lock(
81
+ "gush_enqueue_outgoing_jobs_#{workflow_id}-#{job_name}",
82
+ sleep: configuration.polling_interval,
83
+ block: configuration.locking_duration
84
+ ) do
77
85
  out = client.find_job(workflow_id, job_name)
78
86
 
79
87
  if out.ready_to_start?
data/lib/gush/workflow.rb CHANGED
@@ -112,7 +112,8 @@ module Gush
112
112
  workflow_id: id,
113
113
  id: client.next_free_job_id(id, klass.to_s),
114
114
  params: opts.fetch(:params, {}),
115
- queue: opts[:queue]
115
+ queue: opts[:queue],
116
+ wait: opts[:wait]
116
117
  })
117
118
 
118
119
  jobs << node
@@ -5,9 +5,10 @@ describe "Workflows" do
5
5
  context "when all jobs finish successfuly" do
6
6
  it "marks workflow as completed" do
7
7
  flow = TestWorkflow.create
8
- perform_enqueued_jobs do
9
- flow.start!
10
- end
8
+
9
+ ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
10
+ flow.start!
11
+ ActiveJob::Base.queue_adapter.perform_enqueued_jobs = false
11
12
 
12
13
  flow = flow.reload
13
14
  expect(flow).to be_finished
@@ -152,17 +153,15 @@ describe "Workflows" do
152
153
  flow = PayloadWorkflow.create
153
154
  flow.start!
154
155
 
155
- perform_one
156
- expect(flow.reload.find_job(flow.jobs[0].name).output_payload).to eq('first')
156
+ 3.times { perform_one }
157
157
 
158
- perform_one
159
- expect(flow.reload.find_job(flow.jobs[1].name).output_payload).to eq('second')
158
+ outputs = flow.reload.jobs.select { |j| j.klass == 'RepetitiveJob' }.map { |j| j.output_payload }
159
+ expect(outputs).to match_array(['first', 'second', 'third'])
160
160
 
161
161
  perform_one
162
- expect(flow.reload.find_job(flow.jobs[2].name).output_payload).to eq('third')
163
162
 
164
- perform_one
165
- expect(flow.reload.find_job(flow.jobs[3].name).output_payload).to eq(%w(first second third))
163
+ summary_job = flow.reload.jobs.find { |j| j.klass == 'SummaryJob' }
164
+ expect(summary_job.output_payload).to eq(%w(first second third))
166
165
  end
167
166
 
168
167
  it "does not execute `configure` on each job for huge workflows" do
@@ -37,6 +37,18 @@ describe Gush::Client do
37
37
  end
38
38
 
39
39
  describe "#start_workflow" do
40
+ context "when there is wait parameter configured" do
41
+ let(:freeze_time) { Time.utc(2023, 01, 21, 14, 36, 0) }
42
+
43
+ it "schedules job execution" do
44
+ travel_to freeze_time do
45
+ workflow = WaitableTestWorkflow.create
46
+ client.start_workflow(workflow)
47
+ expect(Gush::Worker).to have_a_job_enqueued_at(workflow.id, job_with_id("Prepare"), 5.minutes)
48
+ end
49
+ end
50
+ end
51
+
40
52
  it "enqueues next jobs from the workflow" do
41
53
  workflow = TestWorkflow.create
42
54
  expect {
@@ -113,7 +125,7 @@ describe Gush::Client do
113
125
  describe "#persist_job" do
114
126
  it "persists JSON dump of the job in Redis" do
115
127
 
116
- job = BobJob.new(name: 'bob')
128
+ job = BobJob.new(name: 'bob', id: 'abcd123')
117
129
 
118
130
  client.persist_job('deadbeef', job)
119
131
  expect(redis.keys("gush.jobs.deadbeef.*").length).to eq(1)
@@ -8,6 +8,8 @@ describe Gush::Configuration do
8
8
  expect(subject.concurrency).to eq(5)
9
9
  expect(subject.namespace).to eq('gush')
10
10
  expect(subject.gushfile).to eq(GUSHFILE.realpath)
11
+ expect(subject.locking_duration).to eq(2)
12
+ expect(subject.polling_interval).to eq(0.3)
11
13
  end
12
14
 
13
15
  describe "#configure" do
@@ -15,10 +17,14 @@ describe Gush::Configuration do
15
17
  Gush.configure do |config|
16
18
  config.redis_url = "redis://localhost"
17
19
  config.concurrency = 25
20
+ config.locking_duration = 5
21
+ config.polling_interval = 0.5
18
22
  end
19
23
 
20
24
  expect(Gush.configuration.redis_url).to eq("redis://localhost")
21
25
  expect(Gush.configuration.concurrency).to eq(25)
26
+ expect(Gush.configuration.locking_duration).to eq(5)
27
+ expect(Gush.configuration.polling_interval).to eq(0.5)
22
28
  end
23
29
  end
24
30
  end
@@ -10,26 +10,43 @@ describe Gush::Graph do
10
10
  edge = double("edge", :[]= => true)
11
11
  graph = double("graph", node: node, edge: edge)
12
12
  path = Pathname.new(Dir.tmpdir).join(filename)
13
- expect(graph).to receive(:start).with(shape: 'diamond', fillcolor: '#CFF09E')
14
- expect(graph).to receive(:end).with(shape: 'diamond', fillcolor: '#F56991')
15
-
16
- expect(graph).to receive(:output).with(png: path.to_s)
17
-
18
- expect(graph).to receive(:add_nodes).with(/Prepare/, label: "Prepare")
19
- expect(graph).to receive(:add_nodes).with(/FetchFirstJob/, label: "FetchFirstJob")
20
- expect(graph).to receive(:add_nodes).with(/FetchSecondJob/, label: "FetchSecondJob")
21
- expect(graph).to receive(:add_nodes).with(/NormalizeJob/, label: "NormalizeJob")
22
- expect(graph).to receive(:add_nodes).with(/PersistFirstJob/, label: "PersistFirstJob")
23
-
24
- expect(graph).to receive(:add_edges).with(nil, /Prepare/)
25
- expect(graph).to receive(:add_edges).with(/Prepare/, /FetchFirstJob/)
26
- expect(graph).to receive(:add_edges).with(/Prepare/, /FetchSecondJob/)
27
- expect(graph).to receive(:add_edges).with(/FetchFirstJob/, /PersistFirstJob/)
28
- expect(graph).to receive(:add_edges).with(/FetchSecondJob/, /NormalizeJob/)
29
- expect(graph).to receive(:add_edges).with(/PersistFirstJob/, /NormalizeJob/)
30
- expect(graph).to receive(:add_edges).with(/NormalizeJob/, nil)
31
-
32
- expect(GraphViz).to receive(:new).and_yield(graph)
13
+
14
+ expect(Graphviz::Graph).to receive(:new).and_return(graph)
15
+
16
+ node_start = double('start')
17
+ node_end = double('end')
18
+ node_prepare = double('Prepare')
19
+ node_fetch_first_job = double('FetchFirstJob')
20
+ node_fetch_second_job = double('FetchSecondJob')
21
+ node_normalize_job = double('NormalizeJob')
22
+ node_persist_first_job = double('PersistFirstJob')
23
+
24
+ expect(graph).to receive(:add_node).with('start', {shape: 'diamond', fillcolor: '#CFF09E', color: "#555555", style: 'filled'}).and_return(node_start)
25
+ expect(graph).to receive(:add_node).with('end', {shape: 'diamond', fillcolor: '#F56991', color: "#555555", style: 'filled'}).and_return(node_end)
26
+
27
+ standard_options = {:color=>"#555555", :fillcolor=>"white", :label=>"Prepare", :shape=>"ellipse", :style=>"filled"}
28
+
29
+ expect(graph).to receive(:add_node).with(/Prepare/, standard_options.merge(label: "Prepare")).and_return(node_prepare)
30
+ expect(graph).to receive(:add_node).with(/FetchFirstJob/, standard_options.merge(label: "FetchFirstJob")).and_return(node_fetch_first_job)
31
+ expect(graph).to receive(:add_node).with(/FetchSecondJob/, standard_options.merge(label: "FetchSecondJob")).and_return(node_fetch_second_job)
32
+ expect(graph).to receive(:add_node).with(/NormalizeJob/, standard_options.merge(label: "NormalizeJob")).and_return(node_normalize_job)
33
+ expect(graph).to receive(:add_node).with(/PersistFirstJob/, standard_options.merge(label: "PersistFirstJob")).and_return(node_persist_first_job)
34
+
35
+ edge_options = {
36
+ dir: "forward",
37
+ penwidth: 1,
38
+ color: "#555555"
39
+ }
40
+
41
+ expect(node_start).to receive(:connect).with(node_prepare, **edge_options)
42
+ expect(node_prepare).to receive(:connect).with(node_fetch_first_job, **edge_options)
43
+ expect(node_prepare).to receive(:connect).with(node_fetch_second_job, **edge_options)
44
+ expect(node_fetch_first_job).to receive(:connect).with(node_persist_first_job, **edge_options)
45
+ expect(node_fetch_second_job).to receive(:connect).with(node_normalize_job, **edge_options)
46
+ expect(node_persist_first_job).to receive(:connect).with(node_normalize_job, **edge_options)
47
+ expect(node_normalize_job).to receive(:connect).with(node_end, **edge_options)
48
+
49
+ expect(graph).to receive(:dump_graph).and_return(nil)
33
50
 
34
51
  subject.viz
35
52
  end
@@ -4,6 +4,8 @@ describe Gush::Worker do
4
4
  subject { described_class.new }
5
5
 
6
6
  let!(:workflow) { TestWorkflow.create }
7
+ let(:locking_duration) { 5 }
8
+ let(:polling_interval) { 0.5 }
7
9
  let!(:job) { client.find_job(workflow.id, "Prepare") }
8
10
  let(:config) { Gush.configuration.to_json }
9
11
  let!(:client) { Gush::Client.new }
@@ -71,5 +73,11 @@ describe Gush::Worker do
71
73
 
72
74
  subject.perform(workflow.id, 'OkayJob')
73
75
  end
76
+
77
+ it 'calls RedisMutex.with_lock with customizable locking_duration and polling_interval' do
78
+ expect(RedisMutex).to receive(:with_lock)
79
+ .with(anything, block: 5, sleep: 0.5).twice
80
+ subject.perform(workflow.id, 'Prepare')
81
+ end
74
82
  end
75
83
  end
@@ -121,6 +121,13 @@ describe Gush::Workflow do
121
121
  expect(flow.jobs.first.params).to eq ({ something: 1 })
122
122
  end
123
123
 
124
+ it "allows passing wait param to the job" do
125
+ flow = Gush::Workflow.new
126
+ flow.run(Gush::Job, wait: 5.seconds)
127
+ flow.save
128
+ expect(flow.jobs.first.wait).to eq (5.seconds)
129
+ end
130
+
124
131
  context "when graph is empty" do
125
132
  it "adds new job with the given class as a node" do
126
133
  flow = Gush::Workflow.new
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
+ require 'active_support'
2
+ require 'active_support/testing/time_helpers'
1
3
  require 'gush'
2
- require 'fakeredis'
3
4
  require 'json'
4
5
  require 'pry'
5
6
 
@@ -35,12 +36,13 @@ class ParameterTestWorkflow < Gush::Workflow
35
36
  end
36
37
  end
37
38
 
38
- class Redis
39
- def publish(*)
39
+ class WaitableTestWorkflow < Gush::Workflow
40
+ def configure
41
+ run Prepare, wait: 5.minutes
40
42
  end
41
43
  end
42
44
 
43
- REDIS_URL = "redis://localhost:6379/12"
45
+ REDIS_URL = ENV["REDIS_URL"] || "redis://localhost:6379/12"
44
46
 
45
47
  module GushHelpers
46
48
  def redis
@@ -91,7 +93,22 @@ RSpec::Matchers.define :have_no_jobs do |flow, jobs|
91
93
  end
92
94
  end
93
95
 
96
+ RSpec::Matchers.define :have_a_job_enqueued_at do |flow, job, at|
97
+ expected_execution_timestamp = (Time.current.utc + at).to_i
98
+
99
+ match do |actual|
100
+ expected = hash_including(args: include(flow, job), at: expected_execution_timestamp)
101
+
102
+ expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to match_array(expected)
103
+ end
104
+
105
+ failure_message do |actual|
106
+ "expected to have enqueued job #{job} to be executed at #{Time.current.utc + at}, but instead has: #{Time.at(enqueued_jobs.first[:at]).to_datetime.utc}"
107
+ end
108
+ end
109
+
94
110
  RSpec.configure do |config|
111
+ config.include ActiveSupport::Testing::TimeHelpers
95
112
  config.include ActiveJob::TestHelper
96
113
  config.include GushHelpers
97
114
 
@@ -104,12 +121,13 @@ RSpec.configure do |config|
104
121
  clear_performed_jobs
105
122
 
106
123
  Gush.configure do |config|
107
- config.redis_url = REDIS_URL
108
- config.gushfile = GUSHFILE
124
+ config.redis_url = REDIS_URL
125
+ config.gushfile = GUSHFILE
126
+ config.locking_duration = defined?(locking_duration) ? locking_duration : 2
127
+ config.polling_interval = defined?(polling_interval) ? polling_interval : 0.3
109
128
  end
110
129
  end
111
130
 
112
-
113
131
  config.after(:each) do
114
132
  clear_enqueued_jobs
115
133
  clear_performed_jobs
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gush
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotrek Okoński
8
- autorequire:
8
+ - Michał Krzyżanowski
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2022-03-10 00:00:00.000000000 Z
12
+ date: 2024-02-29 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: activejob
@@ -16,20 +17,20 @@ dependencies:
16
17
  requirements:
17
18
  - - ">="
18
19
  - !ruby/object:Gem::Version
19
- version: 4.2.7
20
+ version: 6.1.0
20
21
  - - "<"
21
22
  - !ruby/object:Gem::Version
22
- version: '7.0'
23
+ version: '7.2'
23
24
  type: :runtime
24
25
  prerelease: false
25
26
  version_requirements: !ruby/object:Gem::Requirement
26
27
  requirements:
27
28
  - - ">="
28
29
  - !ruby/object:Gem::Version
29
- version: 4.2.7
30
+ version: 6.1.0
30
31
  - - "<"
31
32
  - !ruby/object:Gem::Version
32
- version: '7.0'
33
+ version: '7.2'
33
34
  - !ruby/object:Gem::Dependency
34
35
  name: concurrent-ruby
35
36
  requirement: !ruby/object:Gem::Requirement
@@ -67,7 +68,7 @@ dependencies:
67
68
  version: '3.2'
68
69
  - - "<"
69
70
  - !ruby/object:Gem::Version
70
- version: '5'
71
+ version: '6'
71
72
  type: :runtime
72
73
  prerelease: false
73
74
  version_requirements: !ruby/object:Gem::Requirement
@@ -77,7 +78,7 @@ dependencies:
77
78
  version: '3.2'
78
79
  - - "<"
79
80
  - !ruby/object:Gem::Version
80
- version: '5'
81
+ version: '6'
81
82
  - !ruby/object:Gem::Dependency
82
83
  name: redis-mutex
83
84
  requirement: !ruby/object:Gem::Requirement
@@ -107,7 +108,7 @@ dependencies:
107
108
  - !ruby/object:Gem::Version
108
109
  version: '0.6'
109
110
  - !ruby/object:Gem::Dependency
110
- name: ruby-graphviz
111
+ name: graphviz
111
112
  requirement: !ruby/object:Gem::Requirement
112
113
  requirements:
113
114
  - - "~>"
@@ -124,44 +125,56 @@ dependencies:
124
125
  name: terminal-table
125
126
  requirement: !ruby/object:Gem::Requirement
126
127
  requirements:
127
- - - "~>"
128
+ - - ">="
128
129
  - !ruby/object:Gem::Version
129
130
  version: '1.4'
131
+ - - "<"
132
+ - !ruby/object:Gem::Version
133
+ version: '3.1'
130
134
  type: :runtime
131
135
  prerelease: false
132
136
  version_requirements: !ruby/object:Gem::Requirement
133
137
  requirements:
134
- - - "~>"
138
+ - - ">="
135
139
  - !ruby/object:Gem::Version
136
140
  version: '1.4'
141
+ - - "<"
142
+ - !ruby/object:Gem::Version
143
+ version: '3.1'
137
144
  - !ruby/object:Gem::Dependency
138
- name: colorize
145
+ name: paint
139
146
  requirement: !ruby/object:Gem::Requirement
140
147
  requirements:
141
148
  - - "~>"
142
149
  - !ruby/object:Gem::Version
143
- version: '0.7'
150
+ version: '2.2'
144
151
  type: :runtime
145
152
  prerelease: false
146
153
  version_requirements: !ruby/object:Gem::Requirement
147
154
  requirements:
148
155
  - - "~>"
149
156
  - !ruby/object:Gem::Version
150
- version: '0.7'
157
+ version: '2.2'
151
158
  - !ruby/object:Gem::Dependency
152
159
  name: thor
153
160
  requirement: !ruby/object:Gem::Requirement
154
161
  requirements:
155
- - - "~>"
162
+ - - ">="
156
163
  - !ruby/object:Gem::Version
157
164
  version: '0.19'
165
+ - - "<"
166
+ - !ruby/object:Gem::Version
167
+ version: '1.3'
158
168
  type: :runtime
159
169
  prerelease: false
160
170
  version_requirements: !ruby/object:Gem::Requirement
161
171
  requirements:
162
- - - "~>"
172
+ - - ">="
163
173
  - !ruby/object:Gem::Version
164
174
  version: '0.19'
175
+ - - "<"
176
+ - !ruby/object:Gem::Version
177
+ version: '1.3'
165
178
  - !ruby/object:Gem::Dependency
166
179
  name: launchy
167
180
  requirement: !ruby/object:Gem::Requirement
@@ -196,14 +209,14 @@ dependencies:
196
209
  requirements:
197
210
  - - "~>"
198
211
  - !ruby/object:Gem::Version
199
- version: '10.4'
212
+ version: '12'
200
213
  type: :development
201
214
  prerelease: false
202
215
  version_requirements: !ruby/object:Gem::Requirement
203
216
  requirements:
204
217
  - - "~>"
205
218
  - !ruby/object:Gem::Version
206
- version: '10.4'
219
+ version: '12'
207
220
  - !ruby/object:Gem::Dependency
208
221
  name: rspec
209
222
  requirement: !ruby/object:Gem::Requirement
@@ -232,32 +245,19 @@ dependencies:
232
245
  - - "~>"
233
246
  - !ruby/object:Gem::Version
234
247
  version: '0.10'
235
- - !ruby/object:Gem::Dependency
236
- name: fakeredis
237
- requirement: !ruby/object:Gem::Requirement
238
- requirements:
239
- - - "~>"
240
- - !ruby/object:Gem::Version
241
- version: '0.5'
242
- type: :development
243
- prerelease: false
244
- version_requirements: !ruby/object:Gem::Requirement
245
- requirements:
246
- - - "~>"
247
- - !ruby/object:Gem::Version
248
- version: '0.5'
249
248
  description: Gush is a parallel workflow runner using Redis as storage and ActiveJob
250
249
  for executing jobs.
251
250
  email:
252
251
  - piotrek@okonski.org
252
+ - michal.krzyzanowski+github@gmail.com
253
253
  executables:
254
254
  - gush
255
255
  extensions: []
256
256
  extra_rdoc_files: []
257
257
  files:
258
+ - ".github/workflows/ruby.yml"
258
259
  - ".gitignore"
259
260
  - ".rspec"
260
- - ".travis.yml"
261
261
  - CHANGELOG.md
262
262
  - Gemfile
263
263
  - LICENSE.txt
@@ -274,6 +274,7 @@ files:
274
274
  - lib/gush/graph.rb
275
275
  - lib/gush/job.rb
276
276
  - lib/gush/json.rb
277
+ - lib/gush/version.rb
277
278
  - lib/gush/worker.rb
278
279
  - lib/gush/workflow.rb
279
280
  - spec/Gushfile
@@ -292,7 +293,7 @@ homepage: https://github.com/chaps-io/gush
292
293
  licenses:
293
294
  - MIT
294
295
  metadata: {}
295
- post_install_message:
296
+ post_install_message:
296
297
  rdoc_options: []
297
298
  require_paths:
298
299
  - lib
@@ -300,15 +301,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
300
301
  requirements:
301
302
  - - ">="
302
303
  - !ruby/object:Gem::Version
303
- version: '0'
304
+ version: 3.0.0
304
305
  required_rubygems_version: !ruby/object:Gem::Requirement
305
306
  requirements:
306
307
  - - ">="
307
308
  - !ruby/object:Gem::Version
308
309
  version: '0'
309
310
  requirements: []
310
- rubygems_version: 3.1.4
311
- signing_key:
311
+ rubygems_version: 3.4.22
312
+ signing_key:
312
313
  specification_version: 4
313
314
  summary: Fast and distributed workflow runner based on ActiveJob and Redis
314
315
  test_files:
data/.travis.yml DELETED
@@ -1,16 +0,0 @@
1
- language: ruby
2
- script: "bundle exec rspec"
3
- rvm:
4
- - 2.2.2
5
- - 2.3.4
6
- - 2.4.1
7
- - 2.5
8
- - 2.6
9
- - 2.7
10
- services:
11
- - redis-server
12
- email:
13
- recipients:
14
- - piotrek@okonski.org
15
- on_success: change
16
- on_failure: always