dwf 0.1.6 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a08560cdfefa1c9f8e1f42416599e94550de0f0e1492bb5b05424e2bf94a482f
4
- data.tar.gz: 84a83b6849b64e7fe5da74f5734e2d8faa96df25eef63a5d0de78229fcc733ba
3
+ metadata.gz: fba0bf3a5195787db6540ddfe2431a265b09b78859f333ed9295b4e4677d0a25
4
+ data.tar.gz: 66488a6107dec45933fc1631e713db3fb43a6e31dbd5d5a351a79a6bbb31f1f7
5
5
  SHA512:
6
- metadata.gz: 6eb847d86872dfa70c1a602a3ae8ef0e0ca166cfa22d0a5e3706454ef0144ac434b48489a916d811e9dc66ebb9ef4979b00b63dee8da4d08c44af6d1bd714dad
7
- data.tar.gz: cda011bc87919531f5de78721c3640c2460119cb4dd5668bd4ebb8093e19d46e848d27cfc2112c10021bd41e52e2365ced092f779a25a1509086fdf7095221a9
6
+ metadata.gz: 88c01767c12ae11ff5f2aaeaf68b08a8c04a54bb909c51babd0f02cd915b1c8d790131c197034bbce46a8ab4d8e0dfa40a0f7afe4f1353bff65d952654438454
7
+ data.tar.gz: 799827548d718f4ae763c00c4519f636f448eb0aba40e6d5a60f83443cf2f479b3d8fbfd536744e11fc9092d22be3a18a7bb764b25cafcb27eccef3168ab0170
@@ -8,10 +8,6 @@ on:
8
8
  branches: [ master ]
9
9
  paths:
10
10
  - 'dwf.gemspec'
11
- pull_request:
12
- branches: [ master ]
13
- paths:
14
- - 'dwf.gemspec'
15
11
 
16
12
  jobs:
17
13
  build:
@@ -3,7 +3,7 @@ name: Test
3
3
  on:
4
4
  push:
5
5
  branches:
6
- - '**'
6
+ - 'master'
7
7
  pull_request:
8
8
  branches:
9
9
  - '**'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,86 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
+ ## 0.1.10
4
+ ### Added
5
+ - Allow to use argument within workflow and update the defining callback way
6
+ ```
7
+ class TestWf < Dwf::Workflow
8
+ def configure(arguments)
9
+ run A
10
+ run B, after: A, params: argument
11
+ run C, after: A, params: argument
12
+ end
13
+ end
14
+
15
+ wf = TestWf.create(arguments)
16
+ wf.callback_type = Dwf::Workflow::SK_BATCH
17
+
18
+ ```
19
+ - Support `find` workflow and `reload` workflow
20
+ ```
21
+ wf = TestWf.create
22
+ Dwf::Workflow.find(wf.id)
23
+ wf.reload
24
+ ```
25
+
26
+ ## 0.1.9
27
+ ### Added
28
+ ### Fixed
29
+ - fix incorrect argument at configuration
30
+
31
+ ## 0.1.8
32
+ ### Added
33
+ - add pinlining feature
34
+
35
+ ```ruby
36
+ class SendOutput < Dwf::Item
37
+ def perform
38
+ output('it works')
39
+ end
40
+ end
41
+
42
+ ```
43
+
44
+ `output` method used to output data from the job to add outgoing jobs
45
+
46
+ ```ruby
47
+ class ReceiveOutput < Dwf::Item
48
+ def perform
49
+ message = payloads.first[:output] # it works
50
+ end
51
+ end
52
+ ```
53
+
54
+ `payloads` is an array that containing outputs from incoming jobs
55
+
56
+ ```
57
+ [
58
+ {
59
+ id: "SendOutput|1849a3f9-5fce-401e-a73a-91fc1048356",
60
+ class: "SendOutput",
61
+ output: 'it works'
62
+ }
63
+ ]
64
+ ```
65
+
66
+ ```ruby
67
+ Dwf.config do |config|
68
+ config.opts = { url 'redis://127.0.0.1:6379' }
69
+ config.namespace = 'dwf'
70
+ end
71
+ ```
72
+
73
+ ## 0.1.7
74
+ ### Added
75
+ - Allow to config redis and queue
76
+
77
+ ```ruby
78
+ Dwf.config do |config|
79
+ config.opts = { url 'redis://127.0.0.1:6379' }
80
+ config.namespace = 'dwf'
81
+ end
82
+ ```
83
+
3
84
  ## 0.1.6
4
85
  ### Added
5
86
  - Sidekiq batch callback: separate batches
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  # Installation
5
5
  ## 1. Add `dwf` to Gemfile
6
6
  ```ruby
7
- gem 'dwf', '~> 0.1.5'
7
+ gem 'dwf', '~> 0.1.9'
8
8
  ```
9
9
  ## 2. Execute flow
10
10
  ### Declare jobs
@@ -38,6 +38,14 @@ class TestWf < Dwf::Workflow
38
38
  end
39
39
  ```
40
40
 
41
+
42
+ ### Execute flow
43
+ ```ruby
44
+ wf = TestWf.create
45
+ wf.callback_type = Dwf::Workflow::SK_BATCH
46
+ wf.start!
47
+ ```
48
+
41
49
  #### Note
42
50
  `dwf` supports 2 callback types `Dwf::Workflow::BUILD_IN` and `Dwf::Workflow::SK_BATCH`
43
51
  - `Dwf::Workflow::BUILD_IN` is a build-in callback
@@ -45,12 +53,6 @@ end
45
53
 
46
54
  By default `dwf` will use `Dwf::Workflow::BUILD_IN` callback.
47
55
 
48
- ### Execute flow
49
- ```ruby
50
- wf = TestWf.create(callback_type: Dwf::Workflow::SK_BATCH)
51
- wf.start!
52
- ```
53
-
54
56
  ### Output
55
57
  ```
56
58
  A Working
@@ -70,14 +72,65 @@ D say hello
70
72
  D Finished
71
73
  ```
72
74
 
75
+ # Config redis and default queue
76
+ `dwf` uses redis as the key value stograge through [redis-rb](https://github.com/redis/redis-rb), So you can pass redis configuration by `redis_opts`
77
+ ```ruby
78
+ Dwf.config do |config|
79
+ SENTINELS = [
80
+ { host: "127.0.0.1", port: 26380 },
81
+ { host: "127.0.0.1", port: 26381 }
82
+ ]
83
+ config.redis_opts = { host: 'mymaster', sentinels: SENTINELS, role: :master }
84
+ config.namespace = 'dwf'
85
+ end
86
+ ```
87
+
88
+ # Pinelining
89
+ You can pass jobs result to next nodes
90
+
91
+ ```ruby
92
+ class SendOutput < Dwf::Item
93
+ def perform
94
+ output('it works')
95
+ end
96
+ end
97
+
98
+ ```
99
+
100
+ `output` method used to output data from the job to add outgoing jobs
101
+
102
+ ```ruby
103
+ class ReceiveOutput < Dwf::Item
104
+ def perform
105
+ message = payloads.first[:output] # it works
106
+ end
107
+ end
108
+ ```
109
+
110
+ `payloads` is an array that containing outputs from incoming jobs
111
+
112
+ ```ruby
113
+ [
114
+ {
115
+ id: "SendOutput|1849a3f9-5fce-401e-a73a-91fc1048356",
116
+ class: "SendOutput",
117
+ output: 'it works'
118
+ }
119
+ ]
120
+ ```
121
+
73
122
  # Todo
74
123
  - [x] Make it work
75
124
  - [x] Support pass params
76
125
  - [x] Support with build-in callback
77
126
  - [x] Add github workflow
78
- - [ ] [WIP] Test
79
- - [ ] Transfer output through each node
127
+ - [x] Redis configurable
128
+ - [x] Pinelining
129
+ - [X] Test
130
+ - [ ] Consistent item name
80
131
  - [ ] Support [Resque](https://github.com/resque/resque)
132
+ - [ ] Key value store plugable
133
+ - [ ] research https://github.com/moneta-rb/moneta
81
134
 
82
135
  # References
83
136
  - https://github.com/chaps-io/gush
data/dwf.gemspec CHANGED
@@ -6,7 +6,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
 
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = "dwf"
9
- spec.version = '0.1.6'
9
+ spec.version = '0.1.10'
10
10
  spec.authors = ["dthtien"]
11
11
  spec.email = ["tiendt2311@gmail.com"]
12
12
 
@@ -27,5 +27,6 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency 'byebug', '~> 11.1.3'
28
28
  spec.add_dependency 'redis', '~> 4.2.0'
29
29
  spec.add_development_dependency 'rspec', '~> 3.2'
30
+ spec.add_development_dependency 'mock_redis', '~> 0.27.2'
30
31
  spec.add_dependency 'sidekiq', '~> 6.2.0'
31
32
  end
data/lib/dwf/callback.rb CHANGED
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  require_relative 'client'
2
3
 
3
4
  module Dwf
4
5
  class Callback
6
+ DEFAULT_KEY = 'default_key'
7
+
5
8
  def process_next_step(status, options)
6
9
  previous_job_names = options['names']
7
10
  workflow_id = options['workflow_id']
@@ -12,7 +15,7 @@ module Dwf
12
15
  return if processing_job_names.empty?
13
16
 
14
17
  overall = Sidekiq::Batch.new(status.parent_bid)
15
- overall.jobs { setup_batch(processing_job_names, workflow_id) }
18
+ overall.jobs { setup_batches(processing_job_names, workflow_id) }
16
19
  end
17
20
 
18
21
  def start(job)
@@ -21,38 +24,36 @@ module Dwf
21
24
 
22
25
  private
23
26
 
24
- def setup_batch(processing_job_names, workflow_id)
27
+ def setup_batches(processing_job_names, workflow_id)
25
28
  jobs = fetch_jobs(processing_job_names, workflow_id)
26
29
  jobs_classification = classify_jobs jobs
27
30
 
28
- jobs_classification.values.each do |batch_jobs|
29
- batch = Sidekiq::Batch.new
30
- batch.on(
31
- :success,
32
- 'Dwf::Callback#process_next_step',
33
- names: batch_jobs.map(&:klass),
34
- workflow_id: workflow_id
35
- )
36
-
37
- batch.jobs do
38
- batch_jobs.each do |job|
39
- job.persist_and_perform_async! if job.ready_to_start?
40
- end
31
+ jobs_classification.each do |key, batch_jobs|
32
+ with_lock workflow_id, key do
33
+ setup_batch(batch_jobs, workflow_id)
41
34
  end
42
35
  end
43
36
  end
44
37
 
38
+ def setup_batch(jobs, workflow_id)
39
+ batch = Sidekiq::Batch.new
40
+ batch.on(
41
+ :success,
42
+ 'Dwf::Callback#process_next_step',
43
+ names: jobs.map(&:klass),
44
+ workflow_id: workflow_id
45
+ )
46
+ batch.jobs do
47
+ jobs.each { |job| job.persist_and_perform_async! if job.ready_to_start? }
48
+ end
49
+ end
50
+
45
51
  def classify_jobs(jobs)
46
52
  hash = {}
47
53
  jobs.each do |job|
48
54
  outgoing_jobs = job.outgoing
49
- key = outgoing_jobs.empty? ? 'default_key' : outgoing_jobs.join
50
-
51
- if hash[key].nil?
52
- hash[key] = [job]
53
- else
54
- hash[key] = hash[key].push(job)
55
- end
55
+ key = outgoing_jobs.empty? ? DEFAULT_KEY : outgoing_jobs.join
56
+ hash[key] = hash[key].nil? ? [job] : hash[key].push(job)
56
57
  end
57
58
 
58
59
  hash
@@ -64,13 +65,6 @@ module Dwf
64
65
  end.compact
65
66
  end
66
67
 
67
- def perform_job(job_name, workflow_id)
68
- with_lock workflow_id, job_name do
69
- job = client.find_job(workflow_id, job_name)
70
- job.persist_and_perform_async! if job.ready_to_start?
71
- end
72
- end
73
-
74
68
  def with_lock(workflow_id, job_name)
75
69
  client.check_or_lock(workflow_id, job_name)
76
70
  yield
data/lib/dwf/client.rb CHANGED
@@ -1,5 +1,13 @@
1
+ require_relative 'errors'
2
+
1
3
  module Dwf
2
4
  class Client
5
+ attr_reader :config
6
+
7
+ def initialize(config = Dwf.configuration)
8
+ @config = config
9
+ end
10
+
3
11
  def find_job(workflow_id, job_name)
4
12
  job_name_match = /(?<klass>\w*[^-])-(?<identifier>.*)/.match(job_name)
5
13
  data = if job_name_match
@@ -14,6 +22,16 @@ module Dwf
14
22
  Dwf::Item.from_hash(Dwf::Utils.symbolize_keys(data))
15
23
  end
16
24
 
25
+ def find_workflow(id)
26
+ data = redis.get("dwf.workflows.#{id}")
27
+ raise WorkflowNotFound, "Workflow with given id doesn't exist" if data.nil?
28
+
29
+ hash = JSON.parse(data)
30
+ hash = Dwf::Utils.symbolize_keys(hash)
31
+ nodes = parse_nodes(id)
32
+ workflow_from_hash(hash, nodes)
33
+ end
34
+
17
35
  def persist_job(job)
18
36
  redis.hset("dwf.jobs.#{job.workflow_id}.#{job.klass}", job.id, job.as_json)
19
37
  end
@@ -93,8 +111,30 @@ module Dwf
93
111
  job
94
112
  end
95
113
 
114
+ def parse_nodes(id)
115
+ keys = redis.scan_each(match: "dwf.jobs.#{id}.*")
116
+
117
+ keys.map do |key|
118
+ redis.hvals(key).map do |json|
119
+ Dwf::Utils.symbolize_keys JSON.parse(json)
120
+ end
121
+ end.flatten
122
+ end
123
+
124
+ def workflow_from_hash(hash, nodes = [])
125
+ flow = Module.const_get(hash[:klass]).new(*hash[:arguments])
126
+ flow.jobs = []
127
+ flow.stopped = hash.fetch(:stopped, false)
128
+ flow.id = hash[:id]
129
+ flow.jobs = nodes.map do |node|
130
+ Dwf::Item.from_hash(node)
131
+ end
132
+
133
+ flow
134
+ end
135
+
96
136
  def redis
97
- @redis ||= Redis.new
137
+ @redis ||= Redis.new(config.redis_opts)
98
138
  end
99
139
  end
100
140
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dwf
4
+ class Configuration
5
+ NAMESPACE = 'dwf'
6
+ REDIS_OPTS = { url: 'redis://localhost:6379' }.freeze
7
+
8
+ attr_accessor :redis_opts, :namespace
9
+
10
+ def initialize(hash = {})
11
+ @namespace = hash.fetch(:namespace, NAMESPACE)
12
+ @redis_opts = hash.fetch(:redis_opts, REDIS_OPTS)
13
+ end
14
+ end
15
+ end
data/lib/dwf/errors.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Dwf
2
+ class WorkflowNotFound < StandardError; end
3
+ end
data/lib/dwf/item.rb CHANGED
@@ -1,27 +1,15 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'client'
3
4
 
4
5
  module Dwf
5
6
  class Item
6
- DEFAULT_QUEUE = 'default'
7
-
8
7
  attr_reader :workflow_id, :id, :params, :queue, :klass, :started_at,
9
- :enqueued_at, :finished_at, :failed_at, :callback_type
8
+ :enqueued_at, :finished_at, :failed_at, :callback_type, :output_payload
10
9
  attr_accessor :incoming, :outgoing
11
10
 
12
11
  def initialize(options = {})
13
- @workflow_id = options[:workflow_id]
14
- @id = options[:id]
15
- @params = options[:params]
16
- @queue = options[:queue] || DEFAULT_QUEUE
17
- @incoming = options[:incoming] || []
18
- @outgoing = options[:outgoing] || []
19
- @klass = options[:klass] || self.class
20
- @failed_at = options[:failed_at]
21
- @finished_at = options[:finished_at]
22
- @enqueued_at = options[:enqueued_at]
23
- @started_at = options[:started_at]
24
- @callback_type = options[:callback_type]
12
+ assign_attributes(options)
25
13
  end
26
14
 
27
15
  def self.from_hash(hash)
@@ -40,14 +28,24 @@ module Dwf
40
28
  callback_type == Dwf::Workflow::BUILD_IN
41
29
  end
42
30
 
31
+ def reload
32
+ item = client.find_job(workflow_id, name)
33
+ assign_attributes(item.to_hash)
34
+ end
35
+
43
36
  def perform_async
44
- Dwf::Worker.set(queue: queue).perform_async(workflow_id, name)
37
+ Dwf::Worker.set(queue: queue || client.config.namespace)
38
+ .perform_async(workflow_id, name)
45
39
  end
46
40
 
47
41
  def name
48
42
  @name ||= "#{klass}|#{id}"
49
43
  end
50
44
 
45
+ def output(data)
46
+ @output_payload = data
47
+ end
48
+
51
49
  def no_dependencies?
52
50
  incoming.empty?
53
51
  end
@@ -58,6 +56,17 @@ module Dwf
58
56
  end
59
57
  end
60
58
 
59
+ def payloads
60
+ incoming.map do |job_name|
61
+ job = client.find_job(workflow_id, job_name)
62
+ {
63
+ id: job.name,
64
+ class: job.klass.to_s,
65
+ output: job.output_payload
66
+ }
67
+ end
68
+ end
69
+
61
70
  def enqueue!
62
71
  @enqueued_at = current_timestamp
63
72
  @started_at = nil
@@ -142,7 +151,8 @@ module Dwf
142
151
  failed_at: failed_at,
143
152
  params: params,
144
153
  workflow_id: workflow_id,
145
- callback_type: callback_type
154
+ callback_type: callback_type,
155
+ output_payload: output_payload
146
156
  }
147
157
  end
148
158
 
@@ -159,5 +169,21 @@ module Dwf
159
169
  def client
160
170
  @client ||= Dwf::Client.new
161
171
  end
172
+
173
+ def assign_attributes(options)
174
+ @workflow_id = options[:workflow_id]
175
+ @id = options[:id]
176
+ @params = options[:params]
177
+ @queue = options[:queue]
178
+ @incoming = options[:incoming] || []
179
+ @outgoing = options[:outgoing] || []
180
+ @klass = options[:klass] || self.class
181
+ @failed_at = options[:failed_at]
182
+ @finished_at = options[:finished_at]
183
+ @enqueued_at = options[:enqueued_at]
184
+ @started_at = options[:started_at]
185
+ @callback_type = options[:callback_type]
186
+ @output_payload = options[:output_payload]
187
+ end
162
188
  end
163
189
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dwf
4
+ VERSION = '0.1.8'
5
+ end
data/lib/dwf/worker.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'sidekiq'
2
4
  require_relative 'client'
3
5
 
@@ -7,7 +9,7 @@ module Dwf
7
9
 
8
10
  def perform(workflow_id, job_name)
9
11
  job = client.find_job(workflow_id, job_name)
10
- return job.enqueue_outgoing_jobs if job. succeeded?
12
+ return job.enqueue_outgoing_jobs if job.succeeded?
11
13
 
12
14
  job.mark_as_started
13
15
  job.perform
data/lib/dwf/workflow.rb CHANGED
@@ -10,8 +10,8 @@ module Dwf
10
10
  BUILD_IN = 'build-in',
11
11
  SK_BATCH = 'sk-batch'
12
12
  ].freeze
13
- attr_reader :dependencies, :jobs, :started_at, :finished_at, :persisted, :stopped,
14
- :callback_type
13
+ attr_accessor :jobs, :callback_type, :stopped, :id
14
+ attr_reader :dependencies, :started_at, :finished_at, :persisted, :arguments
15
15
 
16
16
  class << self
17
17
  def create(*args)
@@ -19,41 +19,58 @@ module Dwf
19
19
  flow.save
20
20
  flow
21
21
  end
22
+
23
+ def find(id)
24
+ Dwf::Client.new.find_workflow(id)
25
+ end
22
26
  end
23
27
 
24
- def initialize(options = {})
28
+ def initialize(*args)
25
29
  @dependencies = []
26
- @id = id
30
+ @id = build_id
27
31
  @jobs = []
28
32
  @persisted = false
29
33
  @stopped = false
30
- @callback_type = options[:callback_type] || BUILD_IN
34
+ @arguments = args
35
+ @callback_type = BUILD_IN
31
36
 
32
37
  setup
33
38
  end
34
39
 
40
+ def persist!
41
+ client.persist_workflow(self)
42
+ jobs.each(&:persist!)
43
+ mark_as_persisted
44
+ true
45
+ end
46
+
47
+ alias save persist!
48
+
35
49
  def start!
50
+ mark_as_started
51
+ persist!
36
52
  initial_jobs.each do |job|
37
53
  cb_build_in? ? job.persist_and_perform_async! : Dwf::Callback.new.start(job)
38
54
  end
39
55
  end
40
56
 
41
- def save
42
- client.persist_workflow(self)
43
- jobs.each(&:persist!)
44
- mark_as_persisted
45
- true
57
+ def reload
58
+ flow = self.class.find(id)
59
+ self.stopped = flow.stopped
60
+ self.jobs = flow.jobs
61
+
62
+ self
46
63
  end
47
64
 
48
65
  def cb_build_in?
49
66
  callback_type == BUILD_IN
50
67
  end
51
68
 
52
- def id
53
- @id ||= client.build_workflow_id
69
+ def build_id
70
+ client.build_workflow_id
54
71
  end
55
72
 
56
- def configure; end
73
+ def configure(*arguments); end
57
74
 
58
75
  def run(klass, options = {})
59
76
  node = klass.new(
@@ -140,7 +157,6 @@ module Dwf
140
157
  @stopped = false
141
158
  end
142
159
 
143
-
144
160
  private
145
161
 
146
162
  def initial_jobs
@@ -148,7 +164,7 @@ module Dwf
148
164
  end
149
165
 
150
166
  def setup
151
- configure
167
+ configure(*arguments)
152
168
  resolve_dependencies
153
169
  end
154
170
 
data/lib/dwf.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "bundler/setup"
3
4
 
4
5
  require 'sidekiq'
@@ -12,8 +13,15 @@ require_relative 'dwf/item'
12
13
  require_relative 'dwf/client'
13
14
  require_relative 'dwf/worker'
14
15
  require_relative 'dwf/callback'
16
+ require_relative 'dwf/configuration'
15
17
 
16
18
  module Dwf
17
- VERSION = '0.1.6'
19
+ def self.configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def self.config
24
+ yield configuration
25
+ end
18
26
  end
19
27
 
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'mock_redis'
5
+
6
+ describe Dwf::Client, client: true do
7
+ let(:client) { described_class.new }
8
+ let(:workflow_id) { SecureRandom.uuid }
9
+ let(:id) { SecureRandom.uuid }
10
+ let(:redis) { Redis.new }
11
+ before do
12
+ redis_instance = MockRedis.new
13
+ allow(Redis).to receive(:new).and_return redis_instance
14
+ end
15
+
16
+ describe '#find_job' do
17
+ let!(:job) do
18
+ j = Dwf::Item.new(workflow_id: workflow_id, id: id)
19
+ j.persist!
20
+ j
21
+ end
22
+
23
+ context 'find by item class name' do
24
+ it {
25
+ item = client.find_job(workflow_id, Dwf::Item.name)
26
+ expect(item.workflow_id).to eq workflow_id
27
+ expect(item.id).to eq id
28
+ expect(item.name).to eq job.name
29
+ }
30
+ end
31
+
32
+ context 'find by item name' do
33
+ it {
34
+ item = client.find_job(workflow_id, job.name)
35
+ expect(item.workflow_id).to eq workflow_id
36
+ expect(item.id).to eq id
37
+ expect(item.name).to eq job.name
38
+ }
39
+ end
40
+ end
41
+
42
+ describe '#find_workflow' do
43
+ before do
44
+ wf = Dwf::Workflow.new
45
+ wf.id = workflow_id
46
+ wf.save
47
+ j = Dwf::Item.new(id: id, workflow_id: workflow_id)
48
+ j.persist!
49
+ end
50
+
51
+ it do
52
+ wf = client.find_workflow(workflow_id)
53
+
54
+ expect(wf).not_to be_nil
55
+ expect(wf.jobs.first).to be_kind_of(Dwf::Item)
56
+ end
57
+
58
+ it do
59
+ expect do
60
+ client.find_workflow(SecureRandom.uuid)
61
+ end.to raise_error Dwf::WorkflowNotFound
62
+ end
63
+ end
64
+
65
+ describe '#persist_job' do
66
+ let!(:job) { Dwf::Item.new(workflow_id: workflow_id, id: id) }
67
+
68
+ it do
69
+ expect(redis.exists?("dwf.jobs.#{job.workflow_id}.#{job.klass}"))
70
+ .to be_falsy
71
+
72
+ client.persist_job(job)
73
+
74
+ expect(redis.exists?("dwf.jobs.#{job.workflow_id}.#{job.klass}"))
75
+ .to be_truthy
76
+ end
77
+ end
78
+
79
+ describe '#persist_workflow' do
80
+ let(:workflow) { Dwf::Workflow.new }
81
+
82
+ it do
83
+ expect(redis.exists?("dwf.workflows.#{workflow.id}")).to be_falsy
84
+ client.persist_workflow(workflow)
85
+ expect(redis.exists?("dwf.workflows.#{workflow.id}")).to be_truthy
86
+ end
87
+ end
88
+
89
+ describe '#check_or_lock' do
90
+ before do
91
+ allow_any_instance_of(described_class).to receive(:sleep)
92
+ end
93
+
94
+ context 'job is running' do
95
+ let(:job_name) { 'ahihi' }
96
+
97
+ before do
98
+ allow(client).to receive(:set)
99
+ redis.set("wf_enqueue_outgoing_jobs_#{workflow_id}-#{job_name}", 'running')
100
+ client.check_or_lock(workflow_id, job_name)
101
+ end
102
+
103
+ it { expect(client).not_to have_received(:set) }
104
+ end
105
+
106
+ context 'job is not running' do
107
+ let(:job_name) { 'ahihi' }
108
+
109
+ before do
110
+ allow(redis).to receive(:set)
111
+ client.check_or_lock(workflow_id, job_name)
112
+ end
113
+
114
+ it do
115
+ expect(redis).to have_received(:set)
116
+ .with("wf_enqueue_outgoing_jobs_#{workflow_id}-#{job_name}", 'running')
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '#release_lock' do
122
+ before do
123
+ allow(redis).to receive(:del)
124
+ client.release_lock(workflow_id, 'ahihi')
125
+ end
126
+
127
+ it do
128
+ expect(redis).to have_received(:del)
129
+ .with("dwf_enqueue_outgoing_jobs_#{workflow_id}-ahihi")
130
+ end
131
+ end
132
+
133
+ describe '#build_job_id' do
134
+ before do
135
+ allow(redis).to receive(:hexists)
136
+ client.build_job_id(workflow_id, 'ahihi')
137
+ end
138
+
139
+ it { expect(redis).to have_received(:hexists) }
140
+ end
141
+
142
+ describe '#build_workflow_id' do
143
+ before do
144
+ allow(redis).to receive(:exists?)
145
+ client.build_workflow_id
146
+ end
147
+
148
+ it { expect(redis).to have_received(:exists?) }
149
+ end
150
+
151
+ describe '#key_exists?' do
152
+ before do
153
+ allow(redis).to receive(:exists?)
154
+ client.key_exists?('ahihi')
155
+ end
156
+
157
+ it { expect(redis).to have_received(:exists?).with('ahihi') }
158
+ end
159
+
160
+ describe '#set' do
161
+ before do
162
+ allow(redis).to receive(:set)
163
+ client.set('ahihi', 'a')
164
+ end
165
+
166
+ it { expect(redis).to have_received(:set).with('ahihi', 'a') }
167
+ end
168
+
169
+ describe '#delete' do
170
+ before do
171
+ allow(redis).to receive(:del)
172
+ client.delete('ahihi')
173
+ end
174
+
175
+ it { expect(redis).to have_received(:del).with('ahihi') }
176
+ end
177
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Dwf::Configuration, configuration: true do
6
+ let(:configuration) { described_class.new }
7
+
8
+ specify do
9
+ expect(configuration.namespace).to eq described_class::NAMESPACE
10
+ expect(configuration.redis_opts).to eq described_class::REDIS_OPTS
11
+ end
12
+ end
@@ -16,7 +16,7 @@ describe Dwf::Item, item: true do
16
16
  params: {},
17
17
  incoming: incoming,
18
18
  outgoing: outgoing,
19
- queue: Dwf::Item::DEFAULT_QUEUE,
19
+ queue: Dwf::Configuration::NAMESPACE,
20
20
  klass: 'Dwf::Item',
21
21
  started_at: started_at,
22
22
  finished_at: finished_at,
@@ -181,4 +181,40 @@ describe Dwf::Item, item: true do
181
181
  it { expect(a_item).not_to have_received(:persist_and_perform_async!) }
182
182
  end
183
183
  end
184
+
185
+ describe '#output' do
186
+ before { item.output(1) }
187
+
188
+ it { expect(item.output_payload).to eq 1 }
189
+ end
190
+
191
+ describe '#payloads' do
192
+ let(:incoming) { ["A|#{SecureRandom.uuid}"] }
193
+ let(:client_double) { double(find_job: nil) }
194
+ let!(:a_item) do
195
+ described_class.new(
196
+ workflow_id: SecureRandom.uuid,
197
+ id: SecureRandom.uuid,
198
+ finished_at: finished_at,
199
+ output_payload: 1
200
+ )
201
+ end
202
+
203
+ before do
204
+ allow(Dwf::Client).to receive(:new).and_return client_double
205
+ allow(client_double)
206
+ .to receive(:find_job).and_return a_item
207
+ end
208
+
209
+ it do
210
+ expected_payload = [
211
+ {
212
+ class: a_item.class.name,
213
+ id: a_item.name,
214
+ output: 1
215
+ }
216
+ ]
217
+ expect(item.payloads).to eq expected_payload
218
+ end
219
+ end
184
220
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'mock_redis'
5
+ require 'sidekiq/testing'
6
+
7
+ describe Dwf::Worker, client: true do
8
+ let(:workflow_id) { SecureRandom.uuid }
9
+ let(:id) { SecureRandom.uuid }
10
+ let(:redis) { Redis.new }
11
+ let(:worker) { described_class.perform_async(workflow_id, job.name) }
12
+ before do
13
+ redis_instance = MockRedis.new
14
+ allow(Redis).to receive(:new).and_return redis_instance
15
+ end
16
+
17
+ describe '#find_job' do
18
+ let!(:job) do
19
+ j = Dwf::Item.new(workflow_id: workflow_id, id: id)
20
+ j.persist!
21
+ j
22
+ end
23
+
24
+ before do
25
+ worker
26
+ Sidekiq::Worker.drain_all
27
+ job.reload
28
+ end
29
+
30
+ it { expect(job.finished?).to be_truthy }
31
+ end
32
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'mock_redis'
5
+ class AItem < Dwf::Item; end
6
+
7
+ class BItem < Dwf::Item; end
8
+
9
+ class CItem < Dwf::Item; end
10
+
11
+ describe Dwf::Workflow, workflow: true do
12
+ let(:workflow_id) { SecureRandom.uuid }
13
+ let(:item_id) { SecureRandom.uuid }
14
+ let(:client) do
15
+ double(
16
+ persist_workflow: nil,
17
+ persist_job: nil,
18
+ build_workflow_id: workflow_id,
19
+ build_job_id: item_id,
20
+ find_workflow: nil
21
+ )
22
+ end
23
+ before do
24
+ allow(Dwf::Client).to receive(:new).and_return client
25
+ end
26
+
27
+ describe '#create' do
28
+ it do
29
+ workflow = described_class.create
30
+ expect(client).to have_received(:persist_workflow)
31
+ .with(an_instance_of(described_class))
32
+ expect(workflow.id).to eq workflow_id
33
+ expect(workflow.persisted).to be_truthy
34
+ end
35
+ end
36
+
37
+ describe '#find' do
38
+ before { Dwf::Workflow.find(workflow_id) }
39
+
40
+ it { expect(client).to have_received(:find_workflow).with(workflow_id) }
41
+ end
42
+
43
+ describe '#persist!' do
44
+ let(:workflow) { described_class.new }
45
+ let(:job) do
46
+ Dwf::Item.new(
47
+ worflow_id: workflow_id,
48
+ id: item_id
49
+ )
50
+ end
51
+ before do
52
+ workflow.jobs << job
53
+ workflow.persist!
54
+ end
55
+
56
+ it do
57
+ expect(client).to have_received(:persist_workflow)
58
+ .with(an_instance_of(described_class))
59
+ expect(client).to have_received(:persist_job).with(job)
60
+ expect(workflow.id).to eq workflow_id
61
+ expect(workflow.persisted).to be_truthy
62
+ end
63
+ end
64
+
65
+ describe '#start' do
66
+ let(:workflow) { described_class.new }
67
+ let(:job) do
68
+ Dwf::Item.new(
69
+ worflow_id: workflow_id,
70
+ id: item_id
71
+ )
72
+ end
73
+ before do
74
+ workflow.jobs << job
75
+ workflow.persist!
76
+ end
77
+
78
+ it do
79
+ expect(client).to have_received(:persist_workflow)
80
+ .with(an_instance_of(described_class))
81
+ expect(client).to have_received(:persist_job).with(job)
82
+ expect(workflow.id).to eq workflow_id
83
+ expect(workflow.persisted).to be_truthy
84
+ expect(workflow.stopped).to be_falsy
85
+ end
86
+ end
87
+
88
+ describe '#run' do
89
+ let!(:workflow) { described_class.new }
90
+
91
+ before do
92
+ workflow.run AItem, after: BItem, before: CItem
93
+ end
94
+
95
+ it do
96
+ expect(workflow.jobs).not_to be_empty
97
+ expected = [
98
+ {
99
+ from: BItem.to_s,
100
+ to: "AItem|#{item_id}"
101
+ },
102
+ {
103
+ from: "AItem|#{item_id}",
104
+ to: CItem.to_s
105
+ }
106
+ ]
107
+ expect(workflow.dependencies).to match_array expected
108
+ end
109
+ end
110
+
111
+ describe '#find_job' do
112
+ let!(:workflow) { described_class.new }
113
+ before do
114
+ workflow.jobs = [
115
+ AItem.new,
116
+ BItem.new(id: item_id),
117
+ CItem.new
118
+ ]
119
+ end
120
+
121
+ it 'searches by klass' do
122
+ job = workflow.find_job('AItem')
123
+ expect(job).to be_kind_of AItem
124
+ end
125
+
126
+ it 'searches by name' do
127
+ job = workflow.find_job("BItem|#{item_id}")
128
+ expect(job).to be_kind_of BItem
129
+ end
130
+ end
131
+
132
+ describe '#setup' do
133
+ let!(:workflow) { described_class.new }
134
+ before do
135
+ workflow.run AItem
136
+ workflow.run BItem, after: AItem
137
+ workflow.run CItem, after: BItem
138
+
139
+ workflow.send(:setup)
140
+ end
141
+
142
+ it do
143
+ job_a = workflow.find_job('AItem')
144
+
145
+ expect(job_a.incoming).to be_empty
146
+ expect(job_a.outgoing).to eq ["BItem|#{item_id}"]
147
+
148
+ job_b = workflow.find_job('BItem')
149
+
150
+ expect(job_b.incoming).to eq ['AItem']
151
+ expect(job_b.outgoing).to eq ["CItem|#{item_id}"]
152
+
153
+ job_c = workflow.find_job('CItem')
154
+
155
+ expect(job_c.incoming).to eq ['BItem']
156
+ expect(job_c.outgoing).to be_empty
157
+ end
158
+ end
159
+
160
+ describe '#callback_type' do
161
+ let!(:workflow) { described_class.new }
162
+
163
+ it {
164
+ expect(workflow.callback_type).to eq described_class::BUILD_IN
165
+ workflow.callback_type = described_class::SK_BATCH
166
+ expect(workflow.callback_type).to eq described_class::SK_BATCH
167
+ }
168
+ end
169
+
170
+ describe '#reload' do
171
+ let!(:workflow) do
172
+ flow = described_class.new
173
+ flow.id = workflow_id
174
+ flow
175
+ end
176
+
177
+ before do
178
+ allow(client).to receive(:find_workflow).and_return workflow
179
+ workflow.reload
180
+ end
181
+
182
+ it { expect(client).to have_received(:find_workflow).with(workflow_id) }
183
+ end
184
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dwf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - dthtien
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-06 00:00:00.000000000 Z
11
+ date: 2021-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mock_redis
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.27.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.27.2
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: sidekiq
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,12 +100,19 @@ files:
86
100
  - lib/dwf.rb
87
101
  - lib/dwf/callback.rb
88
102
  - lib/dwf/client.rb
103
+ - lib/dwf/configuration.rb
104
+ - lib/dwf/errors.rb
89
105
  - lib/dwf/item.rb
90
106
  - lib/dwf/utils.rb
107
+ - lib/dwf/version.rb
91
108
  - lib/dwf/worker.rb
92
109
  - lib/dwf/workflow.rb
110
+ - spec/dwf/client_spec.rb
111
+ - spec/dwf/configuration_spec.rb
93
112
  - spec/dwf/item_spec.rb
94
113
  - spec/dwf/utils_spec.rb
114
+ - spec/dwf/worker_spec.rb
115
+ - spec/dwf/workflow_spec.rb
95
116
  - spec/spec_helper.rb
96
117
  homepage: https://github.com/dthtien/wf
97
118
  licenses:
@@ -118,6 +139,10 @@ specification_version: 4
118
139
  summary: Gush cloned without ActiveJob but requried Sidekiq. This project is for researching
119
140
  DSL purpose
120
141
  test_files:
142
+ - spec/dwf/client_spec.rb
143
+ - spec/dwf/configuration_spec.rb
121
144
  - spec/dwf/item_spec.rb
122
145
  - spec/dwf/utils_spec.rb
146
+ - spec/dwf/worker_spec.rb
147
+ - spec/dwf/workflow_spec.rb
123
148
  - spec/spec_helper.rb