dwf 0.1.6 → 0.1.10

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: 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