durable_flow 0.1.0 → 0.2.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 +4 -4
- data/README.md +92 -17
- data/app/controllers/durable_flow/workflow_runs_controller.rb +136 -0
- data/app/views/durable_flow/workflow_runs/definition.html.erb +145 -0
- data/app/views/durable_flow/workflow_runs/index.html.erb +4 -0
- data/app/views/durable_flow/workflow_runs/show.html.erb +4 -1
- data/app/views/layouts/durable_flow/application.html.erb +88 -1
- data/config/routes.rb +5 -1
- data/lib/durable_flow/child_workflow_builder.rb +26 -0
- data/lib/durable_flow/definition_analyzer.rb +342 -0
- data/lib/durable_flow/definition_graph.rb +99 -0
- data/lib/durable_flow/errors.rb +18 -0
- data/lib/durable_flow/models/workflow_step.rb +26 -0
- data/lib/durable_flow/step_proxy.rb +52 -3
- data/lib/durable_flow/test_helper.rb +40 -0
- data/lib/durable_flow/version.rb +1 -1
- data/lib/durable_flow/workflow.rb +328 -25
- data/lib/durable_flow.rb +6 -0
- metadata +19 -1
|
@@ -10,6 +10,46 @@ module DurableFlow
|
|
|
10
10
|
|
|
11
11
|
attr_reader :workflow_run
|
|
12
12
|
|
|
13
|
+
class << self
|
|
14
|
+
def retry_on(*exceptions, **options, &block)
|
|
15
|
+
return super(*exceptions, **options) unless block
|
|
16
|
+
|
|
17
|
+
super(*exceptions, **options) do |job, error|
|
|
18
|
+
begin
|
|
19
|
+
block.call(job, error)
|
|
20
|
+
ensure
|
|
21
|
+
job.send(:fail_workflow_after_unhandled_error!, error) if job.is_a?(DurableFlow::Workflow)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def discard_on(*exceptions, **options, &block)
|
|
27
|
+
super(*exceptions, **options) do |job, error|
|
|
28
|
+
begin
|
|
29
|
+
block.call(job, error) if block
|
|
30
|
+
ensure
|
|
31
|
+
job.send(:fail_workflow_after_unhandled_error!, error) if job.is_a?(DurableFlow::Workflow)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def perform_now
|
|
38
|
+
super
|
|
39
|
+
rescue Exception => error
|
|
40
|
+
fail_workflow_after_unhandled_error!(error)
|
|
41
|
+
raise
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def retry_job(options = {})
|
|
45
|
+
if options[:error]
|
|
46
|
+
persist_retrying_workflow!(options[:error])
|
|
47
|
+
failed_workflow_step&.retry!(options[:error], retry_at: retry_at_from(options))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
|
|
13
53
|
def checkpoint!
|
|
14
54
|
refresh_execution_lock!
|
|
15
55
|
interrupt!(reason: :stopping) if queue_adapter.respond_to?(:stopping?) && queue_adapter.stopping?
|
|
@@ -25,7 +65,7 @@ module DurableFlow
|
|
|
25
65
|
durable_step(name) do
|
|
26
66
|
step_record = current_workflow_step
|
|
27
67
|
metadata = step_record.metadata_hash
|
|
28
|
-
wake_at =
|
|
68
|
+
wake_at = durable_flow_parse_time(metadata["wake_at"]) || durable_flow_time_from(duration, explicit_time: until_time)
|
|
29
69
|
|
|
30
70
|
raise ArgumentError, "Provide a duration or until: time for sleep step #{name.inspect}" unless wake_at
|
|
31
71
|
|
|
@@ -39,14 +79,15 @@ module DurableFlow
|
|
|
39
79
|
end
|
|
40
80
|
end
|
|
41
81
|
|
|
42
|
-
def wait_for_event_step(name, event_name:, timeout:, match:)
|
|
82
|
+
def wait_for_event_step(name, event_name:, timeout:, match:, allow_past_events: false, &block)
|
|
43
83
|
durable_step(name) do
|
|
44
84
|
step_record = current_workflow_step
|
|
45
85
|
wait = find_or_initialize_wait(step_record, event_name: event_name, timeout: timeout, match: match)
|
|
46
86
|
|
|
47
|
-
if (event = matched_event_for(wait))
|
|
87
|
+
if (event = matched_event_for(wait, allow_past_events: allow_past_events))
|
|
48
88
|
wait.update!(status: "matched", workflow_event: event)
|
|
49
|
-
event.payload_value
|
|
89
|
+
payload = event.payload_value
|
|
90
|
+
block ? block.call(payload) : payload
|
|
50
91
|
elsif wait.timeout_at && Time.current >= wait.timeout_at
|
|
51
92
|
wait.update!(status: "timed_out")
|
|
52
93
|
step_record.update!(status: "failed", metadata: step_record.metadata_hash.merge("timeout_at" => wait.timeout_at.utc.iso8601(9)))
|
|
@@ -73,12 +114,83 @@ module DurableFlow
|
|
|
73
114
|
StepProxy.new(self).wait_for_workflow(name, workflow_or_run_id, timeout: timeout)
|
|
74
115
|
end
|
|
75
116
|
|
|
117
|
+
def child_workflow(name, workflow_class = nil, *args, timeout: nil, on_failure: :raise, **kwargs, &block)
|
|
118
|
+
validate_child_workflow_failure_policy!(on_failure)
|
|
119
|
+
|
|
120
|
+
base_name = name.to_s
|
|
121
|
+
child = start_child_workflow_step("#{base_name}_start", workflow_class, *args, **kwargs, &block)
|
|
122
|
+
|
|
123
|
+
wait_for_child_workflow("#{base_name}_wait", child, timeout: timeout, on_failure: on_failure)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def invoke_workflow(name, workflow_class = nil, *args, timeout: nil, on_failure: :raise, **kwargs, &block)
|
|
127
|
+
child_workflow(name, workflow_class, *args, timeout: timeout, on_failure: on_failure, **kwargs, &block)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def invoke_workflows(name, collection, timeout: nil, concurrency: nil, on_failure: :raise, &block)
|
|
131
|
+
validate_child_workflow_failure_policy!(on_failure)
|
|
132
|
+
raise ArgumentError, "Provide a block that returns workflow requests" unless block
|
|
133
|
+
|
|
134
|
+
requests = collection.map do |item|
|
|
135
|
+
request = block.call(item)
|
|
136
|
+
unless request.respond_to?(:workflow_key)
|
|
137
|
+
raise ArgumentError, "invoke_each blocks must return a workflow request with a stable workflow_key"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
request
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
child_workflows(name, requests, timeout: timeout, concurrency: concurrency, on_failure: on_failure)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def child_workflows(name, collection = nil, key: nil, timeout: nil, concurrency: nil, on_failure: :raise, &block)
|
|
147
|
+
validate_child_workflow_failure_policy!(on_failure)
|
|
148
|
+
|
|
149
|
+
if collection.nil?
|
|
150
|
+
raise ArgumentError, "Provide a child workflow collection or builder block" unless block
|
|
151
|
+
|
|
152
|
+
builder = ChildWorkflowBuilder.new
|
|
153
|
+
block.call(builder)
|
|
154
|
+
collection = builder.requests
|
|
155
|
+
block = nil
|
|
156
|
+
key ||= :workflow_key
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
batches = child_workflow_batches(collection, concurrency)
|
|
160
|
+
|
|
161
|
+
batches.flat_map do |batch|
|
|
162
|
+
children = batch.map do |item|
|
|
163
|
+
item_key = child_workflow_item_key(item, key)
|
|
164
|
+
child = start_child_workflow_step("#{name}_#{item_key}_start") do
|
|
165
|
+
block ? block.call(item) : start_child_workflow_request(item)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
child.merge("key" => item_key)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
children.map do |child|
|
|
172
|
+
completion = wait_for_child_workflow("#{name}_#{child.fetch("key")}_wait", child, timeout: timeout, on_failure: on_failure)
|
|
173
|
+
completion.to_h.with_indifferent_access.merge(
|
|
174
|
+
"key" => child.fetch("key"),
|
|
175
|
+
"run_id" => child.fetch("run_id"),
|
|
176
|
+
"workflow_class" => child["workflow_class"] || completion_value(completion, :workflow_class),
|
|
177
|
+
).compact
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def each_child_workflow(name, collection, key:, timeout: nil, on_failure: :raise, &block)
|
|
183
|
+
raise ArgumentError, "Provide a block that starts each child workflow" unless block
|
|
184
|
+
|
|
185
|
+
child_workflows(name, collection, key: key, timeout: timeout, on_failure: on_failure, &block)
|
|
186
|
+
end
|
|
187
|
+
|
|
76
188
|
def log
|
|
77
189
|
@workflow_logger ||= WorkflowLogger.new(self)
|
|
78
190
|
end
|
|
79
191
|
|
|
80
192
|
private
|
|
81
|
-
attr_reader :current_workflow_step
|
|
193
|
+
attr_reader :current_workflow_step, :failed_workflow_step
|
|
82
194
|
|
|
83
195
|
def continue(&block)
|
|
84
196
|
ensure_workflow_run!
|
|
@@ -94,20 +206,18 @@ module DurableFlow
|
|
|
94
206
|
end
|
|
95
207
|
|
|
96
208
|
mark_workflow_running!
|
|
97
|
-
block.call
|
|
98
|
-
complete_workflow!
|
|
209
|
+
result = block.call
|
|
210
|
+
complete_workflow!(result)
|
|
99
211
|
rescue Pause => pause
|
|
100
212
|
persist_interrupted_workflow!(pause.status)
|
|
101
213
|
rescue ActiveJob::Continuation::Interrupt => interrupt
|
|
102
214
|
resume_job(interrupt)
|
|
103
215
|
rescue ActiveJob::Continuation::Error => error
|
|
104
|
-
fail_workflow!(error)
|
|
105
216
|
raise
|
|
106
217
|
rescue StandardError => error
|
|
107
218
|
if resume_errors_after_advancing? && continuation.advanced?
|
|
108
219
|
resume_job(exception: error)
|
|
109
220
|
else
|
|
110
|
-
fail_workflow!(error)
|
|
111
221
|
raise
|
|
112
222
|
end
|
|
113
223
|
ensure
|
|
@@ -150,6 +260,12 @@ module DurableFlow
|
|
|
150
260
|
value = block.arity == 0 ? block.call : block.call(continuation_step)
|
|
151
261
|
step_record.complete!(value)
|
|
152
262
|
loaded = true
|
|
263
|
+
rescue Pause, ActiveJob::Continuation::Interrupt
|
|
264
|
+
raise
|
|
265
|
+
rescue StandardError => error
|
|
266
|
+
@failed_workflow_step = step_record
|
|
267
|
+
step_record.fail!(error)
|
|
268
|
+
raise
|
|
153
269
|
ensure
|
|
154
270
|
@current_workflow_step = nil
|
|
155
271
|
end
|
|
@@ -219,20 +335,23 @@ module DurableFlow
|
|
|
219
335
|
event_name: event_name.to_s,
|
|
220
336
|
status: "pending",
|
|
221
337
|
match: Serializer.dump(match || {}),
|
|
222
|
-
timeout_at: (
|
|
338
|
+
timeout_at: (durable_flow_time_from(timeout) if timeout),
|
|
223
339
|
)).tap do |wait|
|
|
224
340
|
updates = {}
|
|
225
341
|
updates[:event_name] = event_name.to_s if wait.event_name != event_name.to_s
|
|
226
342
|
updates[:match] = Serializer.dump(match || {}) if wait.match.blank?
|
|
227
|
-
updates[:timeout_at] =
|
|
343
|
+
updates[:timeout_at] = durable_flow_time_from(timeout) if timeout && wait.timeout_at.blank?
|
|
228
344
|
wait.update!(updates) if updates.any?
|
|
229
345
|
end
|
|
230
346
|
end
|
|
231
347
|
|
|
232
|
-
def matched_event_for(wait)
|
|
348
|
+
def matched_event_for(wait, allow_past_events: false)
|
|
233
349
|
return wait.workflow_event if wait.workflow_event
|
|
234
350
|
|
|
235
|
-
WorkflowEvent.named(wait.event_name)
|
|
351
|
+
scope = WorkflowEvent.named(wait.event_name)
|
|
352
|
+
scope = scope.where("created_at >= ?", wait.created_at) unless allow_past_events
|
|
353
|
+
|
|
354
|
+
scope.order(:created_at).detect do |event|
|
|
236
355
|
wait.matches_event?(event)
|
|
237
356
|
end
|
|
238
357
|
end
|
|
@@ -307,7 +426,20 @@ module DurableFlow
|
|
|
307
426
|
)
|
|
308
427
|
end
|
|
309
428
|
|
|
310
|
-
def
|
|
429
|
+
def persist_retrying_workflow!(error)
|
|
430
|
+
ensure_workflow_run!
|
|
431
|
+
|
|
432
|
+
workflow_run.update!(
|
|
433
|
+
status: "retrying",
|
|
434
|
+
interrupted_at: Time.current,
|
|
435
|
+
serialized_job: serialize,
|
|
436
|
+
queue_name: queue_name,
|
|
437
|
+
priority: priority,
|
|
438
|
+
last_error: error_payload(error),
|
|
439
|
+
)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def complete_workflow!(result = nil)
|
|
311
443
|
workflow_run.update!(
|
|
312
444
|
status: "completed",
|
|
313
445
|
completed_at: Time.current,
|
|
@@ -315,11 +447,23 @@ module DurableFlow
|
|
|
315
447
|
last_error: nil,
|
|
316
448
|
)
|
|
317
449
|
|
|
318
|
-
|
|
450
|
+
payload = {
|
|
319
451
|
run_id: workflow_run.run_id,
|
|
320
452
|
job_id: job_id,
|
|
321
453
|
workflow_class: self.class.name,
|
|
322
|
-
|
|
454
|
+
result: result,
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
DurableFlow.notify(DurableFlow::WORKFLOW_COMPLETED_EVENT, payload)
|
|
458
|
+
DurableFlow.notify(DurableFlow::WORKFLOW_FINISHED_EVENT, payload.merge(status: "completed"))
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def fail_workflow_after_unhandled_error!(error)
|
|
462
|
+
return unless workflow_run
|
|
463
|
+
return if workflow_run.terminal?
|
|
464
|
+
|
|
465
|
+
failed_workflow_step&.fail!(error)
|
|
466
|
+
fail_workflow!(error)
|
|
323
467
|
end
|
|
324
468
|
|
|
325
469
|
def fail_workflow!(error)
|
|
@@ -327,23 +471,182 @@ module DurableFlow
|
|
|
327
471
|
status: "failed",
|
|
328
472
|
failed_at: Time.current,
|
|
329
473
|
serialized_job: serialize,
|
|
330
|
-
last_error:
|
|
331
|
-
"class" => error.class.name,
|
|
332
|
-
"message" => error.message,
|
|
333
|
-
"backtrace" => Array(error.backtrace).first(10),
|
|
334
|
-
},
|
|
474
|
+
last_error: error_payload(error),
|
|
335
475
|
)
|
|
336
476
|
|
|
337
|
-
|
|
477
|
+
payload = {
|
|
338
478
|
run_id: workflow_run.run_id,
|
|
339
479
|
job_id: job_id,
|
|
340
480
|
workflow_class: self.class.name,
|
|
341
481
|
error_class: error.class.name,
|
|
342
482
|
error_message: error.message,
|
|
343
|
-
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
DurableFlow.notify(DurableFlow::WORKFLOW_FAILED_EVENT, payload)
|
|
486
|
+
DurableFlow.notify(DurableFlow::WORKFLOW_FINISHED_EVENT, payload.merge(status: "failed"))
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def error_payload(error)
|
|
490
|
+
{
|
|
491
|
+
"class" => error.class.name,
|
|
492
|
+
"message" => error.message,
|
|
493
|
+
"backtrace" => Array(error.backtrace).first(10),
|
|
494
|
+
}
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def retry_at_from(options)
|
|
498
|
+
return options[:wait_until].to_time if options[:wait_until].respond_to?(:to_time)
|
|
499
|
+
return unless options[:wait]
|
|
500
|
+
|
|
501
|
+
Time.current + options[:wait].to_i
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def start_child_workflow_step(name, workflow_class = nil, *args, **kwargs, &block)
|
|
505
|
+
durable_step(name) do
|
|
506
|
+
child = if block
|
|
507
|
+
block.arity.zero? ? block.call : block.call(workflow_class)
|
|
508
|
+
else
|
|
509
|
+
raise ArgumentError, "Provide a workflow class or block for child workflow #{name.inspect}" unless workflow_class
|
|
510
|
+
|
|
511
|
+
workflow_class.perform_later(*args, **kwargs)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
child_workflow_start_payload(child, workflow_class)
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def wait_for_child_workflow(name, child, timeout: nil, on_failure: :raise)
|
|
519
|
+
validate_child_workflow_failure_policy!(on_failure)
|
|
520
|
+
|
|
521
|
+
child = child.to_h.stringify_keys
|
|
522
|
+
wait_for_event_step(
|
|
523
|
+
name,
|
|
524
|
+
event_name: DurableFlow::WORKFLOW_FINISHED_EVENT,
|
|
525
|
+
timeout: timeout,
|
|
526
|
+
match: { run_id: child.fetch("run_id") },
|
|
527
|
+
allow_past_events: true,
|
|
528
|
+
) do |completion|
|
|
529
|
+
if completion_value(completion, :status) == "failed" && on_failure == :raise
|
|
530
|
+
raise_child_workflow_failed!(completion)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
completion
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def validate_child_workflow_failure_policy!(value)
|
|
538
|
+
return if %i[raise return].include?(value)
|
|
539
|
+
|
|
540
|
+
raise ArgumentError, "Child workflow on_failure must be :raise or :return"
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def child_workflow_start_payload(child, workflow_class = nil)
|
|
544
|
+
if child.respond_to?(:to_h)
|
|
545
|
+
payload = child.to_h.with_indifferent_access
|
|
546
|
+
if payload[:run_id].present?
|
|
547
|
+
return {
|
|
548
|
+
"run_id" => payload.fetch(:run_id),
|
|
549
|
+
"workflow_class" => payload[:workflow_class],
|
|
550
|
+
}.compact
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
{
|
|
555
|
+
"run_id" => extract_child_workflow_run_id(child),
|
|
556
|
+
"workflow_class" => child_workflow_class_name(child, workflow_class),
|
|
557
|
+
}.compact
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def extract_child_workflow_run_id(child)
|
|
561
|
+
run_id = child.respond_to?(:job_id) ? child.job_id : child.to_s
|
|
562
|
+
|
|
563
|
+
raise ArgumentError, "Child workflow start must return a job, run id, or object responding to job_id" if run_id.blank?
|
|
564
|
+
|
|
565
|
+
run_id
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def child_workflow_item_key(item, key)
|
|
569
|
+
key ||= :workflow_key
|
|
570
|
+
|
|
571
|
+
value = if key.respond_to?(:call)
|
|
572
|
+
key.call(item)
|
|
573
|
+
elsif item.respond_to?(:fetch)
|
|
574
|
+
item.fetch(key) { item.fetch(key.to_s) }
|
|
575
|
+
else
|
|
576
|
+
item.public_send(key)
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
value.to_s
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def child_workflow_batches(collection, concurrency)
|
|
583
|
+
items = collection.to_a
|
|
584
|
+
return [ items ] unless concurrency
|
|
585
|
+
|
|
586
|
+
size = concurrency.to_i
|
|
587
|
+
raise ArgumentError, "Child workflow concurrency must be at least 1" if size < 1
|
|
588
|
+
|
|
589
|
+
items.each_slice(size).to_a
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def start_child_workflow_request(request)
|
|
593
|
+
if request.respond_to?(:workflow_class)
|
|
594
|
+
workflow_class = request.workflow_class
|
|
595
|
+
args = request.respond_to?(:workflow_args) ? Array(request.workflow_args) : []
|
|
596
|
+
kwargs = child_workflow_request_kwargs(request)
|
|
597
|
+
|
|
598
|
+
return child_workflow_start_payload(workflow_class.perform_later(*args, **kwargs), workflow_class)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
unless request.respond_to?(:perform_later)
|
|
602
|
+
raise ArgumentError, "Child workflow request must respond to perform_later or workflow_class"
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
request.perform_later
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def child_workflow_request_kwargs(request)
|
|
609
|
+
kwargs = if request.respond_to?(:workflow_kwargs)
|
|
610
|
+
request.workflow_kwargs
|
|
611
|
+
elsif request.respond_to?(:workflow_arguments)
|
|
612
|
+
request.workflow_arguments
|
|
613
|
+
else
|
|
614
|
+
{}
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
kwargs ||= {}
|
|
618
|
+
kwargs.respond_to?(:symbolize_keys) ? kwargs.symbolize_keys : kwargs.to_h
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def child_workflow_class_name(child, workflow_class = nil)
|
|
622
|
+
return workflow_class.name if workflow_class.respond_to?(:name)
|
|
623
|
+
return child.class.name if child.respond_to?(:job_id)
|
|
624
|
+
|
|
625
|
+
nil
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def raise_child_workflow_failed!(completion)
|
|
629
|
+
raise ChildWorkflowFailedError.new(
|
|
630
|
+
run_id: completion_value(completion, :run_id),
|
|
631
|
+
workflow_class: completion_value(completion, :workflow_class),
|
|
632
|
+
error_class: completion_value(completion, :error_class),
|
|
633
|
+
error_message: completion_value(completion, :error_message),
|
|
634
|
+
)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def completion_value(payload, key)
|
|
638
|
+
return unless payload.respond_to?(:[])
|
|
639
|
+
|
|
640
|
+
if payload.respond_to?(:key?) && payload.key?(key)
|
|
641
|
+
payload[key]
|
|
642
|
+
elsif payload.respond_to?(:key?) && payload.key?(key.to_s)
|
|
643
|
+
payload[key.to_s]
|
|
644
|
+
else
|
|
645
|
+
payload[key]
|
|
646
|
+
end
|
|
344
647
|
end
|
|
345
648
|
|
|
346
|
-
def
|
|
649
|
+
def durable_flow_time_from(value, explicit_time: nil)
|
|
347
650
|
return explicit_time.to_time if explicit_time.respond_to?(:to_time)
|
|
348
651
|
return nil if value.nil?
|
|
349
652
|
return value.to_time if value.respond_to?(:to_time)
|
|
@@ -351,7 +654,7 @@ module DurableFlow
|
|
|
351
654
|
Time.current + value
|
|
352
655
|
end
|
|
353
656
|
|
|
354
|
-
def
|
|
657
|
+
def durable_flow_parse_time(value)
|
|
355
658
|
return if value.blank?
|
|
356
659
|
return value if value.is_a?(Time)
|
|
357
660
|
|
data/lib/durable_flow.rb
CHANGED
|
@@ -6,6 +6,8 @@ require "active_job/continuation"
|
|
|
6
6
|
require "active_record"
|
|
7
7
|
require "active_support/core_ext/numeric/time"
|
|
8
8
|
require "active_support/core_ext/object/blank"
|
|
9
|
+
require "active_support/core_ext/hash/indifferent_access"
|
|
10
|
+
require "active_support/core_ext/hash/keys"
|
|
9
11
|
require "active_support/core_ext/module/attribute_accessors"
|
|
10
12
|
require "securerandom"
|
|
11
13
|
|
|
@@ -14,8 +16,11 @@ require "durable_flow/errors"
|
|
|
14
16
|
require "durable_flow/serializer"
|
|
15
17
|
require "durable_flow/schema"
|
|
16
18
|
require "durable_flow/live"
|
|
19
|
+
require "durable_flow/definition_graph"
|
|
20
|
+
require "durable_flow/definition_analyzer"
|
|
17
21
|
require "durable_flow/workflow_logger"
|
|
18
22
|
require "durable_flow/workflow_timeline"
|
|
23
|
+
require "durable_flow/child_workflow_builder"
|
|
19
24
|
require "durable_flow/models/application_record"
|
|
20
25
|
require "durable_flow/models/workflow_run"
|
|
21
26
|
require "durable_flow/models/workflow_step"
|
|
@@ -39,6 +44,7 @@ module DurableFlow
|
|
|
39
44
|
|
|
40
45
|
WORKFLOW_COMPLETED_EVENT = "durable_flow.workflow.completed"
|
|
41
46
|
WORKFLOW_FAILED_EVENT = "durable_flow.workflow.failed"
|
|
47
|
+
WORKFLOW_FINISHED_EVENT = "durable_flow.workflow.finished"
|
|
42
48
|
IGNORED_EVENT_NAMESPACES = %w[
|
|
43
49
|
action_controller
|
|
44
50
|
action_mailbox
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: durable_flow
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- DurableFlow contributors
|
|
@@ -89,6 +89,20 @@ dependencies:
|
|
|
89
89
|
- - "<"
|
|
90
90
|
- !ruby/object:Gem::Version
|
|
91
91
|
version: '9.0'
|
|
92
|
+
- !ruby/object:Gem::Dependency
|
|
93
|
+
name: prism
|
|
94
|
+
requirement: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: 1.3.0
|
|
99
|
+
type: :runtime
|
|
100
|
+
prerelease: false
|
|
101
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: 1.3.0
|
|
92
106
|
- !ruby/object:Gem::Dependency
|
|
93
107
|
name: railties
|
|
94
108
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -162,11 +176,15 @@ files:
|
|
|
162
176
|
- MIT-LICENSE
|
|
163
177
|
- README.md
|
|
164
178
|
- app/controllers/durable_flow/workflow_runs_controller.rb
|
|
179
|
+
- app/views/durable_flow/workflow_runs/definition.html.erb
|
|
165
180
|
- app/views/durable_flow/workflow_runs/index.html.erb
|
|
166
181
|
- app/views/durable_flow/workflow_runs/show.html.erb
|
|
167
182
|
- app/views/layouts/durable_flow/application.html.erb
|
|
168
183
|
- config/routes.rb
|
|
169
184
|
- lib/durable_flow.rb
|
|
185
|
+
- lib/durable_flow/child_workflow_builder.rb
|
|
186
|
+
- lib/durable_flow/definition_analyzer.rb
|
|
187
|
+
- lib/durable_flow/definition_graph.rb
|
|
170
188
|
- lib/durable_flow/dispatcher.rb
|
|
171
189
|
- lib/durable_flow/engine.rb
|
|
172
190
|
- lib/durable_flow/errors.rb
|