dynflow 0.8.3 → 0.8.4
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 +8 -8
- data/doc/pages/source/documentation/index.md +14 -14
- data/dynflow.gemspec +0 -1
- data/examples/future_execution.rb +7 -7
- data/lib/dynflow.rb +3 -3
- data/lib/dynflow/action.rb +7 -9
- data/lib/dynflow/action/with_sub_plans.rb +9 -3
- data/lib/dynflow/config.rb +2 -2
- data/lib/dynflow/coordinator.rb +3 -3
- data/lib/dynflow/delayed_executors.rb +9 -0
- data/lib/dynflow/{schedulers → delayed_executors}/abstract.rb +3 -3
- data/lib/dynflow/{schedulers → delayed_executors}/abstract_core.rb +7 -7
- data/lib/dynflow/{schedulers → delayed_executors}/polling.rb +6 -6
- data/lib/dynflow/{scheduled_plan.rb → delayed_plan.rb} +4 -4
- data/lib/dynflow/execution_plan.rb +10 -10
- data/lib/dynflow/execution_plan/output_reference.rb +2 -2
- data/lib/dynflow/execution_plan/steps/error.rb +1 -1
- data/lib/dynflow/execution_plan/steps/plan_step.rb +2 -2
- data/lib/dynflow/middleware.rb +1 -1
- data/lib/dynflow/middleware/stack.rb +1 -1
- data/lib/dynflow/middleware/world.rb +1 -1
- data/lib/dynflow/persistence.rb +10 -10
- data/lib/dynflow/persistence_adapters/abstract.rb +4 -4
- data/lib/dynflow/persistence_adapters/sequel.rb +17 -17
- data/lib/dynflow/persistence_adapters/sequel_migrations/008_rename_scheduled_plans_to_delayed_plans.rb +5 -0
- data/lib/dynflow/serializable.rb +1 -1
- data/lib/dynflow/serializer.rb +1 -1
- data/lib/dynflow/testing/assertions.rb +1 -1
- data/lib/dynflow/utils.rb +205 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web/console_helpers.rb +1 -1
- data/lib/dynflow/web/filtering_helpers.rb +3 -3
- data/lib/dynflow/world.rb +16 -16
- data/test/action_test.rb +4 -2
- data/test/future_execution_test.rb +32 -32
- data/test/middleware_test.rb +5 -5
- data/test/persistence_test.rb +3 -3
- data/test/support/dummy_example.rb +2 -2
- data/test/support/middleware_example.rb +5 -5
- data/test/test_helper.rb +1 -1
- data/test/testing_test.rb +1 -1
- data/web/views/show.erb +3 -3
- metadata +9 -21
- data/lib/dynflow/schedulers.rb +0 -9
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZWZjMGI1OWM2ZWQ4N2JiNDM5MDVmMDA1NmJlODJiMmJmNzg1ZDRjOA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZWFhMTBiOWU0NTVjZTEyMGM5NDlhYTY1NDg0ZDVjYjg5MzJhZDFhMg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDI4ZGVlNjQ5NGZkZGFhY2I3ZTlmZmJkNjRiZTMzODVkMDYzODAwNWNkYzU5
|
10
|
+
OTFjNGRiNWZjYzAwOTdjNDc5ZDcxMjk2MWNlMWVlNzdiYzhlNDVmMWExYTFl
|
11
|
+
MWZjYTAxZjMwODY1NTM2MWZiODliZjU2OTI3NGIyZThkNGU4YTI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
OGEwM2M2YzViODM3Njc2NTExNTE0ZTY5YjA1NjNiNzJhMzIwNDBiZmQ2MmY0
|
14
|
+
NTg1MTBiYzViYjRjYmQ2MzU3YTFhZTdhOWQzZWIxMDYyZDM0NjM4YTZjYjc2
|
15
|
+
YTQyZTU1MWQ0MzcxYjMwNmYwYTM1NmUyOTc0YWQwZjQ1ZTU0OWM=
|
@@ -235,7 +235,7 @@ TriggerResult = Algebrick.type do
|
|
235
235
|
PlaningFailed = type { fields! execution_plan_id: String, error: Exception }
|
236
236
|
# Returned by #trigger when planning is successful but execution fails to start.
|
237
237
|
ExecutionFailed = type { fields! execution_plan_id: String, error: Exception }
|
238
|
-
# Returned by #
|
238
|
+
# Returned by #delay when scheduling succeeded.
|
239
239
|
Scheduled = type { fields! execution_plan_id: String }
|
240
240
|
# Returned by #trigger when planning is successful, #future will resolve after
|
241
241
|
# ExecutionPlan is executed.
|
@@ -273,19 +273,19 @@ end
|
|
273
273
|
#### Scheduling
|
274
274
|
|
275
275
|
Scheduling an action means setting it up to be triggered at set time in future.
|
276
|
-
Any action can be
|
276
|
+
Any action can be delayed by calling:
|
277
277
|
|
278
278
|
```ruby
|
279
|
-
world_instance.
|
280
|
-
|
281
|
-
|
279
|
+
world_instance.delay(AnAction,
|
280
|
+
{ start_at: Time.now + 360, start_before: Time.now + 400 },
|
281
|
+
*args)
|
282
282
|
```
|
283
283
|
|
284
|
-
This snippet of code would
|
284
|
+
This snippet of code would delay `AnAction` with arguments `args` to be executed
|
285
285
|
in the time interval between `start_at` and `start_before`. Setting `start_before` to `nil`
|
286
|
-
would
|
286
|
+
would delay execution of this action without the timeout limit.
|
287
287
|
|
288
|
-
When an action is
|
288
|
+
When an action is delayed, an execution plan object is created with state set
|
289
289
|
to `scheduled`, but it doesn't run the the plan phase yet, the planning happens
|
290
290
|
when the `start_at` time comes. If the planning doesn't happen in time
|
291
291
|
(e.g. after `start_before`), the execution plan is marked as failed
|
@@ -293,12 +293,12 @@ when the `start_at` time comes. If the planning doesn't happen in time
|
|
293
293
|
|
294
294
|
Since the `args` have to be saved, there must be a mechanism to safely serialize and deserialize them
|
295
295
|
in order to make them survive being saved in a database. This is handled by a serializer.
|
296
|
-
Different serializers can be set per action by overriding its `
|
296
|
+
Different serializers can be set per action by overriding its `delay` method.
|
297
297
|
|
298
|
-
Planning of the
|
299
|
-
periodically checks for
|
300
|
-
plans don't do anything by themselves, they just wait to be picked up and planned by a
|
301
|
-
It means that if no
|
298
|
+
Planning of the delayed plans is handled by `DelayedExecutor`, an object which
|
299
|
+
periodically checks for delayed execution plans and plans them. Scheduled execution
|
300
|
+
plans don't do anything by themselves, they just wait to be picked up and planned by a DelayedExecutor.
|
301
|
+
It means that if no DelayedExecutor is present, their planning will be delayed until a scheduler
|
302
302
|
is spawned.
|
303
303
|
|
304
304
|
#### Plan phase
|
@@ -1015,7 +1015,7 @@ client worlds: useful in production, see [develpment vs. production](#developmen
|
|
1015
1015
|
client requests and other worlds
|
1016
1016
|
1. **executor dispatcher** - responsible for getting requests from
|
1017
1017
|
other worlds and sending the responses
|
1018
|
-
1. **
|
1018
|
+
1. **delayed executor** - responsible for planning and exectuion of scheduled tasks
|
1019
1019
|
|
1020
1020
|
{% plantuml %}
|
1021
1021
|
|
data/dynflow.gemspec
CHANGED
@@ -26,7 +26,7 @@ end
|
|
26
26
|
|
27
27
|
class DelayedAction < Dynflow::Action
|
28
28
|
|
29
|
-
def
|
29
|
+
def delay(delay_options, *args)
|
30
30
|
CustomPassedObjectSerializer.new(args)
|
31
31
|
end
|
32
32
|
|
@@ -49,9 +49,9 @@ if $0 == __FILE__
|
|
49
49
|
|
50
50
|
object = CustomPassedObject.new(1, 'CPS')
|
51
51
|
|
52
|
-
past_plan = ExampleHelper.world.
|
53
|
-
near_future_plan = ExampleHelper.world.
|
54
|
-
future_plan = ExampleHelper.world.
|
52
|
+
past_plan = ExampleHelper.world.delay(DelayedAction, { :start_at => past, :start_before => past }, object)
|
53
|
+
near_future_plan = ExampleHelper.world.delay(DelayedAction, { :start_at => near_future, :start_before => future }, object)
|
54
|
+
future_plan = ExampleHelper.world.delay(DelayedAction, { :start_at => future }, object)
|
55
55
|
|
56
56
|
puts <<-MSG.gsub(/^.*\|/, '')
|
57
57
|
|
|
@@ -61,9 +61,9 @@ if $0 == __FILE__
|
|
61
61
|
| This example shows the future execution functionality of Dynflow, which allows to plan actions to be executed at set time.
|
62
62
|
|
|
63
63
|
| Execution plans:
|
64
|
-
| #{past_plan.id} is
|
65
|
-
| #{near_future_plan.id} is
|
66
|
-
| #{future_plan.id} is
|
64
|
+
| #{past_plan.id} is "delayed" to execute before #{past} and should timeout on the first run of the scheduler.
|
65
|
+
| #{near_future_plan.id} is delayed to execute at #{near_future} and should run successfully.
|
66
|
+
| #{future_plan.id} is delayed to execute at #{future} and should run successfully.
|
67
67
|
|
|
68
68
|
| Visit http://localhost:4567 to see their status.
|
69
69
|
|
|
data/lib/dynflow.rb
CHANGED
@@ -2,7 +2,6 @@ require 'apipie-params'
|
|
2
2
|
require 'algebrick'
|
3
3
|
require 'thread'
|
4
4
|
require 'set'
|
5
|
-
require 'active_support/core_ext/hash/indifferent_access'
|
6
5
|
require 'base64'
|
7
6
|
require 'concurrent'
|
8
7
|
require 'concurrent-edge'
|
@@ -22,6 +21,7 @@ module Dynflow
|
|
22
21
|
class Error < StandardError
|
23
22
|
end
|
24
23
|
|
24
|
+
require 'dynflow/utils'
|
25
25
|
require 'dynflow/round_robin'
|
26
26
|
require 'dynflow/actor'
|
27
27
|
require 'dynflow/errors'
|
@@ -36,7 +36,7 @@ module Dynflow
|
|
36
36
|
require 'dynflow/flows'
|
37
37
|
require 'dynflow/execution_history'
|
38
38
|
require 'dynflow/execution_plan'
|
39
|
-
require 'dynflow/
|
39
|
+
require 'dynflow/delayed_plan'
|
40
40
|
require 'dynflow/action'
|
41
41
|
require 'dynflow/executors'
|
42
42
|
require 'dynflow/logger_adapters'
|
@@ -44,6 +44,6 @@ module Dynflow
|
|
44
44
|
require 'dynflow/connectors'
|
45
45
|
require 'dynflow/dispatcher'
|
46
46
|
require 'dynflow/serializers'
|
47
|
-
require 'dynflow/
|
47
|
+
require 'dynflow/delayed_executors'
|
48
48
|
require 'dynflow/config'
|
49
49
|
end
|
data/lib/dynflow/action.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/inflector'
|
2
|
-
|
3
1
|
module Dynflow
|
4
2
|
class Action < Serializable
|
5
3
|
|
@@ -127,13 +125,13 @@ module Dynflow
|
|
127
125
|
def input=(hash)
|
128
126
|
Type! hash, Hash
|
129
127
|
phase! Plan
|
130
|
-
@input = hash
|
128
|
+
@input = Utils.indifferent_hash(hash)
|
131
129
|
end
|
132
130
|
|
133
131
|
def output=(hash)
|
134
132
|
Type! hash, Hash
|
135
133
|
phase! Run
|
136
|
-
@output = hash
|
134
|
+
@output = Utlis.indifferent_hash(hash)
|
137
135
|
end
|
138
136
|
|
139
137
|
def output
|
@@ -282,10 +280,10 @@ module Dynflow
|
|
282
280
|
recursion.(input)
|
283
281
|
end
|
284
282
|
|
285
|
-
def
|
283
|
+
def execute_delay(delay_options, *args)
|
286
284
|
with_error_handling(true) do
|
287
|
-
world.middleware.execute(:
|
288
|
-
@serializer =
|
285
|
+
world.middleware.execute(:delay, self, delay_options, *args) do
|
286
|
+
@serializer = delay(delay_options, *args).tap do |serializer|
|
289
287
|
serializer.perform_serialization!
|
290
288
|
end
|
291
289
|
end
|
@@ -293,7 +291,7 @@ module Dynflow
|
|
293
291
|
end
|
294
292
|
|
295
293
|
def serializer
|
296
|
-
raise "The action must be
|
294
|
+
raise "The action must be delayed in order to access the serializer" if @serializer.nil?
|
297
295
|
@serializer
|
298
296
|
end
|
299
297
|
|
@@ -313,7 +311,7 @@ module Dynflow
|
|
313
311
|
@step.save
|
314
312
|
end
|
315
313
|
|
316
|
-
def
|
314
|
+
def delay(delay_options, *args)
|
317
315
|
Serializers::Noop.new(args)
|
318
316
|
end
|
319
317
|
|
@@ -67,7 +67,8 @@ module Dynflow
|
|
67
67
|
def wait_for_sub_plans(sub_plans)
|
68
68
|
output.update(total_count: 0,
|
69
69
|
failed_count: 0,
|
70
|
-
success_count: 0
|
70
|
+
success_count: 0,
|
71
|
+
pending_count: 0)
|
71
72
|
|
72
73
|
planned, failed = sub_plans.partition(&:planned?)
|
73
74
|
|
@@ -75,6 +76,7 @@ module Dynflow
|
|
75
76
|
|
76
77
|
output[:total_count] = sub_plan_ids.size
|
77
78
|
output[:failed_count] = failed.size
|
79
|
+
output[:pending_count] = planned.size
|
78
80
|
|
79
81
|
if planned.any?
|
80
82
|
notify_on_finish(planned)
|
@@ -123,6 +125,7 @@ module Dynflow
|
|
123
125
|
else
|
124
126
|
output[:failed_count] += 1
|
125
127
|
end
|
128
|
+
output[:pending_count] -= 1
|
126
129
|
end
|
127
130
|
|
128
131
|
def done?
|
@@ -144,7 +147,8 @@ module Dynflow
|
|
144
147
|
def recalculate_counts
|
145
148
|
output.update(total_count: 0,
|
146
149
|
failed_count: 0,
|
147
|
-
success_count: 0
|
150
|
+
success_count: 0,
|
151
|
+
pending_count: 0)
|
148
152
|
sub_plans.each do |sub_plan|
|
149
153
|
output[:total_count] += 1
|
150
154
|
if sub_plan.state == :stopped
|
@@ -153,12 +157,14 @@ module Dynflow
|
|
153
157
|
else
|
154
158
|
output[:success_count] += 1
|
155
159
|
end
|
160
|
+
else
|
161
|
+
output[:pending_count] += 1
|
156
162
|
end
|
157
163
|
end
|
158
164
|
end
|
159
165
|
|
160
166
|
def counts_set?
|
161
|
-
output[:total_count] && output[:success_count] && output[:failed_count]
|
167
|
+
output[:total_count] && output[:success_count] && output[:failed_count] && output[:pending_count]
|
162
168
|
end
|
163
169
|
|
164
170
|
def check_for_errors!
|
data/lib/dynflow/config.rb
CHANGED
@@ -93,10 +93,10 @@ module Dynflow
|
|
93
93
|
true
|
94
94
|
end
|
95
95
|
|
96
|
-
config_attr :
|
96
|
+
config_attr :delayed_executor, DelayedExecutors::Abstract, NilClass do |world|
|
97
97
|
options = { :poll_interval => 15,
|
98
98
|
:time_source => -> { Time.now.utc } }
|
99
|
-
|
99
|
+
DelayedExecutors::Polling.new(world, options)
|
100
100
|
end
|
101
101
|
|
102
102
|
config_attr :action_classes do
|
data/lib/dynflow/coordinator.rb
CHANGED
@@ -41,7 +41,7 @@ module Dynflow
|
|
41
41
|
|
42
42
|
def initialize(*args)
|
43
43
|
@data ||= {}
|
44
|
-
@data = @data.merge(class: self.class.name)
|
44
|
+
@data = Utils.indifferent_hash(@data.merge(class: self.class.name))
|
45
45
|
end
|
46
46
|
|
47
47
|
def from_hash(hash)
|
@@ -157,10 +157,10 @@ module Dynflow
|
|
157
157
|
|
158
158
|
end
|
159
159
|
|
160
|
-
class
|
160
|
+
class DelayedExecutorLock < LockByWorld
|
161
161
|
def initialize(world)
|
162
162
|
super
|
163
|
-
@data[:id] = "
|
163
|
+
@data[:id] = "delayed-executor"
|
164
164
|
end
|
165
165
|
end
|
166
166
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Dynflow
|
2
|
-
module
|
2
|
+
module DelayedExecutors
|
3
3
|
class Abstract
|
4
4
|
|
5
5
|
attr_reader :core
|
@@ -26,7 +26,7 @@ module Dynflow
|
|
26
26
|
|
27
27
|
def spawn
|
28
28
|
Concurrent.future.tap do |initialized|
|
29
|
-
@core = core_class.spawn name: '
|
29
|
+
@core = core_class.spawn name: 'delayed-executor',
|
30
30
|
args: [@world, @options],
|
31
31
|
initialized: initialized
|
32
32
|
end
|
@@ -34,4 +34,4 @@ module Dynflow
|
|
34
34
|
|
35
35
|
end
|
36
36
|
end
|
37
|
-
end
|
37
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Dynflow
|
2
|
-
module
|
2
|
+
module DelayedExecutors
|
3
3
|
class AbstractCore < Actor
|
4
4
|
|
5
5
|
include Algebrick::TypeCheck
|
@@ -19,7 +19,7 @@ module Dynflow
|
|
19
19
|
@time_source = options.fetch(:time_source, -> { Time.now.utc })
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def check_delayed_plans
|
23
23
|
raise NotImplementedError
|
24
24
|
end
|
25
25
|
|
@@ -29,9 +29,9 @@ module Dynflow
|
|
29
29
|
@time_source.call()
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def delayed_execution_plans(time)
|
33
33
|
with_error_handling([]) do
|
34
|
-
world.persistence.
|
34
|
+
world.persistence.find_past_delayed_plans(time)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -42,9 +42,9 @@ module Dynflow
|
|
42
42
|
error_retval
|
43
43
|
end
|
44
44
|
|
45
|
-
def process(
|
45
|
+
def process(delayed_plans, check_time)
|
46
46
|
processed_plan_uuids = []
|
47
|
-
|
47
|
+
delayed_plans.each do |plan|
|
48
48
|
with_error_handling do
|
49
49
|
if !plan.start_before.nil? && plan.start_before < check_time
|
50
50
|
@logger.debug "Failing plan #{plan.execution_plan_uuid}"
|
@@ -57,7 +57,7 @@ module Dynflow
|
|
57
57
|
processed_plan_uuids << plan.execution_plan_uuid
|
58
58
|
end
|
59
59
|
end
|
60
|
-
world.persistence.
|
60
|
+
world.persistence.delete_delayed_plans(:execution_plan_uuid => processed_plan_uuids) unless processed_plan_uuids.empty?
|
61
61
|
end
|
62
62
|
|
63
63
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Dynflow
|
2
|
-
module
|
2
|
+
module DelayedExecutors
|
3
3
|
class Polling < Abstract
|
4
4
|
|
5
5
|
def core_class
|
6
|
-
Dynflow::
|
6
|
+
Dynflow::DelayedExecutors::PollingCore
|
7
7
|
end
|
8
8
|
|
9
9
|
end
|
@@ -17,15 +17,15 @@ module Dynflow
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def start
|
20
|
-
|
20
|
+
check_delayed_plans
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def check_delayed_plans
|
24
24
|
check_time = time
|
25
|
-
plans =
|
25
|
+
plans = delayed_execution_plans(check_time)
|
26
26
|
process plans, check_time
|
27
27
|
|
28
|
-
world.clock.ping(self, poll_interval, :
|
28
|
+
world.clock.ping(self, poll_interval, :check_delayed_plans)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Dynflow
|
2
|
-
class
|
2
|
+
class DelayedPlan < Serializable
|
3
3
|
|
4
4
|
include Algebrick::TypeCheck
|
5
5
|
|
@@ -37,8 +37,8 @@ module Dynflow
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def cancel
|
40
|
-
error("
|
41
|
-
@world.persistence.
|
40
|
+
error("Delayed task cancelled", "Delayed task cancelled")
|
41
|
+
@world.persistence.delete_delayed_plans(:execution_plan_uuid => execution_plan.id)
|
42
42
|
return true
|
43
43
|
end
|
44
44
|
|
@@ -56,7 +56,7 @@ module Dynflow
|
|
56
56
|
|
57
57
|
# @api private
|
58
58
|
def self.new_from_hash(world, hash, *args)
|
59
|
-
serializer = hash[:args_serializer].
|
59
|
+
serializer = Utils.constantize(hash[:args_serializer]).new(nil, hash[:serialized_args])
|
60
60
|
self.new(world,
|
61
61
|
hash[:execution_plan_uuid],
|
62
62
|
string_to_time(hash[:start_at]),
|
@@ -152,23 +152,23 @@ module Dynflow
|
|
152
152
|
@last_step_id += 1
|
153
153
|
end
|
154
154
|
|
155
|
-
def
|
155
|
+
def delay(action_class, delay_options, *args)
|
156
156
|
save
|
157
157
|
@root_plan_step = add_scheduling_step(action_class)
|
158
|
-
execution_history.add("
|
159
|
-
serializer = root_plan_step.
|
160
|
-
|
158
|
+
execution_history.add("delay", @world.id)
|
159
|
+
serializer = root_plan_step.delay(delay_options, args)
|
160
|
+
delayed_plan = DelayedPlan.new(@world,
|
161
161
|
id,
|
162
|
-
|
163
|
-
|
162
|
+
delay_options[:start_at],
|
163
|
+
delay_options.fetch(:start_before, nil),
|
164
164
|
serializer)
|
165
|
-
persistence.
|
165
|
+
persistence.save_delayed_plan(delayed_plan)
|
166
166
|
ensure
|
167
167
|
update_state(error? ? :stopped : :scheduled)
|
168
168
|
end
|
169
169
|
|
170
|
-
def
|
171
|
-
@
|
170
|
+
def delay_record
|
171
|
+
@delay_record ||= persistence.load_delayed_plan(id)
|
172
172
|
end
|
173
173
|
|
174
174
|
def prepare(action_class, options = {})
|
@@ -208,7 +208,7 @@ module Dynflow
|
|
208
208
|
# array with the future value of the cancel result)
|
209
209
|
def cancel
|
210
210
|
if state == :scheduled
|
211
|
-
[Concurrent.future.tap { |f| f.success
|
211
|
+
[Concurrent.future.tap { |f| f.success delay_record.cancel }]
|
212
212
|
else
|
213
213
|
steps_to_cancel.map do |step|
|
214
214
|
world.event(id, step.id, ::Dynflow::Action::Cancellable::Cancel)
|