chrono_forge 0.1.0 → 0.1.1
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 +1 -13
- data/gemfiles/rails_7.1.gemfile.lock +1 -1
- data/lib/chrono_forge/error_log.rb +0 -5
- data/lib/chrono_forge/execution_log.rb +0 -5
- data/lib/chrono_forge/executor/methods/wait_until.rb +4 -4
- data/lib/chrono_forge/executor/retry_strategy.rb +1 -1
- data/lib/chrono_forge/executor.rb +48 -12
- data/lib/chrono_forge/version.rb +1 -1
- data/lib/chrono_forge/workflow.rb +8 -8
- data/lib/chrono_forge.rb +1 -1
- data/lib/generators/chrono_forge/install/templates/install_chrono_forge.rb +3 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0303e4144f2e7546095c0828a02fa810138b3963603ee33810232cc4444a7ee8
|
4
|
+
data.tar.gz: d1805992395168a7873b34c7264518e0b6d740fcdef23dd5fce2ac2cf686bb3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64d20d4a723c5d751c60415f1461e5e0e581a6401a084d4f6c83cc9c0813f7b7163c87dff916e4af628e7742291cecfc9786d6afc922fc046577713d2db94f13
|
7
|
+
data.tar.gz: 0bcb5a5c37f3fd0e241e0e3d60a358b4fd7d2700c1960a8148b5b12dca147ce8f0db07c7e435d314c7b6bd537e3830ad1f0fc131f33e2cee1bfbf0d60c4a7b63
|
data/README.md
CHANGED
@@ -151,17 +151,6 @@ class MyWorkflow < ApplicationJob
|
|
151
151
|
end
|
152
152
|
```
|
153
153
|
|
154
|
-
### Cleanup
|
155
|
-
|
156
|
-
ChronoForge includes built-in cleanup methods for managing old workflow data:
|
157
|
-
|
158
|
-
```ruby
|
159
|
-
# Clean up old workflows and logs
|
160
|
-
ChronoForge::Workflow.cleanup_old_logs(retention_period: 30.days)
|
161
|
-
ChronoForge::ExecutionLog.cleanup_old_logs(retention_period: 30.days)
|
162
|
-
ChronoForge::ErrorLog.cleanup_old_logs(retention_period: 30.days)
|
163
|
-
```
|
164
|
-
|
165
154
|
## Testing
|
166
155
|
|
167
156
|
ChronoForge is designed to be easily testable using [ChaoticJob](https://github.com/fractaledmind/chaotic_job), a testing framework that makes it simple to test complex job workflows. Here's how to set up your test environment:
|
@@ -186,7 +175,7 @@ require 'chaotic_job'
|
|
186
175
|
Example test:
|
187
176
|
|
188
177
|
```ruby
|
189
|
-
class WorkflowTest <
|
178
|
+
class WorkflowTest < ActiveJob::TestCase
|
190
179
|
include ChaoticJob::Helpers
|
191
180
|
|
192
181
|
def test_workflow_completion
|
@@ -210,7 +199,6 @@ end
|
|
210
199
|
ChaoticJob provides several helpful methods for testing workflows:
|
211
200
|
|
212
201
|
- `perform_all_jobs`: Processes all enqueued jobs, including those enqueued during job execution
|
213
|
-
- `enqueued_jobs`: Returns the current number of jobs in the queue
|
214
202
|
|
215
203
|
For testing with specific job processing libraries like Sidekiq or Delayed Job, you can still use their respective testing modes, but ChaoticJob is recommended for testing ChronoForge workflows as it better handles the complexities of nested job scheduling and wait states.
|
216
204
|
|
@@ -6,8 +6,8 @@ module ChronoForge
|
|
6
6
|
module WaitUntil
|
7
7
|
def wait_until(condition, **options)
|
8
8
|
# Default timeout and check interval
|
9
|
-
timeout = options[:timeout] ||
|
10
|
-
check_interval = options[:check_interval] ||
|
9
|
+
timeout = options[:timeout] || 1.hour
|
10
|
+
check_interval = options[:check_interval] || 15.minutes
|
11
11
|
|
12
12
|
# Find or create execution log
|
13
13
|
step_name = "wait_until$#{condition}"
|
@@ -89,8 +89,8 @@ module ChronoForge
|
|
89
89
|
state: :failed,
|
90
90
|
metadata: metadata.merge("result" => nil)
|
91
91
|
)
|
92
|
-
Rails.logger.warn { "Timeout reached for condition #{condition}.
|
93
|
-
raise WaitConditionNotMet, "Condition not met within timeout period"
|
92
|
+
Rails.logger.warn { "Timeout reached for condition '#{condition}'." }
|
93
|
+
raise WaitConditionNotMet, "Condition '#{condition}' not met within timeout period"
|
94
94
|
end
|
95
95
|
|
96
96
|
# Reschedule with delay
|
@@ -10,7 +10,7 @@ module ChronoForge
|
|
10
10
|
|
11
11
|
include Methods
|
12
12
|
|
13
|
-
def perform(key, attempt: 0, **kwargs)
|
13
|
+
def perform(key, attempt: 0, options: {}, **kwargs)
|
14
14
|
# Prevent excessive retries
|
15
15
|
if attempt >= self.class::RetryStrategy.max_attempts
|
16
16
|
Rails.logger.error { "Max attempts reached for job #{key}" }
|
@@ -18,7 +18,7 @@ module ChronoForge
|
|
18
18
|
end
|
19
19
|
|
20
20
|
# Find or create job with comprehensive tracking
|
21
|
-
setup_workflow(key, kwargs)
|
21
|
+
setup_workflow(key, options, kwargs)
|
22
22
|
|
23
23
|
begin
|
24
24
|
# Skip if workflow cannot be executed
|
@@ -47,13 +47,13 @@ module ChronoForge
|
|
47
47
|
nil
|
48
48
|
rescue => e
|
49
49
|
Rails.logger.error { "An error occurred during execution of #{key}" }
|
50
|
-
self.class::ExecutionTracker.track_error(workflow, e)
|
50
|
+
error_log = self.class::ExecutionTracker.track_error(workflow, e)
|
51
51
|
|
52
52
|
# Retry if applicable
|
53
53
|
if should_retry?(e, attempt)
|
54
54
|
self.class::RetryStrategy.schedule_retry(workflow, attempt: attempt)
|
55
55
|
else
|
56
|
-
|
56
|
+
fail_workflow! error_log
|
57
57
|
end
|
58
58
|
ensure
|
59
59
|
context.save!
|
@@ -71,9 +71,6 @@ module ChronoForge
|
|
71
71
|
step_name: "$workflow_completion$"
|
72
72
|
) do |log|
|
73
73
|
log.started_at = Time.current
|
74
|
-
log.metadata = {
|
75
|
-
workflow_id: workflow.id
|
76
|
-
}
|
77
74
|
end
|
78
75
|
|
79
76
|
begin
|
@@ -104,14 +101,53 @@ module ChronoForge
|
|
104
101
|
end
|
105
102
|
end
|
106
103
|
|
107
|
-
def
|
108
|
-
|
104
|
+
def fail_workflow!(error_log)
|
105
|
+
# Create an execution log for workflow failure
|
106
|
+
execution_log = ExecutionLog.create_or_find_by!(
|
107
|
+
workflow: workflow,
|
108
|
+
step_name: "$workflow_failure$"
|
109
|
+
) do |log|
|
110
|
+
log.started_at = Time.current
|
111
|
+
log.metadata = {
|
112
|
+
error_log_id: error_log.id
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
begin
|
117
|
+
execution_log.update!(
|
118
|
+
attempts: execution_log.attempts + 1,
|
119
|
+
last_executed_at: Time.current
|
120
|
+
)
|
121
|
+
|
122
|
+
workflow.failed!
|
123
|
+
|
124
|
+
# Mark execution log as completed
|
125
|
+
execution_log.update!(
|
126
|
+
state: :completed,
|
127
|
+
completed_at: Time.current
|
128
|
+
)
|
129
|
+
|
130
|
+
# Return the execution log for tracking
|
131
|
+
execution_log
|
132
|
+
rescue => e
|
133
|
+
# Log any completion errors
|
134
|
+
execution_log.update!(
|
135
|
+
state: :failed,
|
136
|
+
error_message: e.message,
|
137
|
+
error_class: e.class.name
|
138
|
+
)
|
139
|
+
raise
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def setup_workflow(key, options, kwargs)
|
144
|
+
@workflow = find_workflow(key, options, kwargs)
|
109
145
|
@context = Context.new(@workflow)
|
110
146
|
end
|
111
147
|
|
112
|
-
def find_workflow(key, kwargs)
|
113
|
-
Workflow.create_or_find_by!(key: key) do |workflow|
|
114
|
-
workflow.
|
148
|
+
def find_workflow(key, options, kwargs)
|
149
|
+
Workflow.create_or_find_by!(job_class: self.class.to_s, key: key) do |workflow|
|
150
|
+
workflow.options = options
|
115
151
|
workflow.kwargs = kwargs
|
116
152
|
workflow.started_at = Time.current
|
117
153
|
end
|
data/lib/chrono_forge/version.rb
CHANGED
@@ -7,9 +7,10 @@
|
|
7
7
|
# id :integer not null, primary key
|
8
8
|
# completed_at :datetime
|
9
9
|
# context :json not null
|
10
|
-
#
|
10
|
+
# job_class :string not null
|
11
11
|
# key :string not null
|
12
12
|
# kwargs :json not null
|
13
|
+
# options :json not null
|
13
14
|
# locked_at :datetime
|
14
15
|
# started_at :datetime
|
15
16
|
# state :integer default("idle"), not null
|
@@ -24,8 +25,8 @@ module ChronoForge
|
|
24
25
|
class Workflow < ActiveRecord::Base
|
25
26
|
self.table_name = "chrono_forge_workflows"
|
26
27
|
|
27
|
-
has_many :execution_logs
|
28
|
-
has_many :error_logs
|
28
|
+
has_many :execution_logs, -> { order(id: :asc) }
|
29
|
+
has_many :error_logs, -> { order(id: :asc) }
|
29
30
|
|
30
31
|
enum :state, %i[
|
31
32
|
idle
|
@@ -35,16 +36,15 @@ module ChronoForge
|
|
35
36
|
stalled
|
36
37
|
]
|
37
38
|
|
38
|
-
# Cleanup method
|
39
|
-
def self.cleanup_old_logs(retention_period: 30.days)
|
40
|
-
where("created_at < ?", retention_period.ago).delete_all
|
41
|
-
end
|
42
|
-
|
43
39
|
# Serialization for metadata
|
44
40
|
serialize :metadata, coder: JSON
|
45
41
|
|
46
42
|
def executable?
|
47
43
|
idle? || running?
|
48
44
|
end
|
45
|
+
|
46
|
+
def job_klass
|
47
|
+
job_class.constantize
|
48
|
+
end
|
49
49
|
end
|
50
50
|
end
|
data/lib/chrono_forge.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
class InstallChronoForge < ActiveRecord::Migration[7.1]
|
4
4
|
def change
|
5
5
|
create_table :chrono_forge_workflows do |t|
|
6
|
-
t.string :key, null: false, index:
|
7
|
-
t.string :
|
6
|
+
t.string :key, null: false, index: true
|
7
|
+
t.string :job_class, null: false
|
8
8
|
|
9
9
|
if t.respond_to?(:jsonb)
|
10
10
|
t.jsonb :kwargs, null: false, default: {}
|
@@ -24,6 +24,7 @@ class InstallChronoForge < ActiveRecord::Migration[7.1]
|
|
24
24
|
t.datetime :completed_at
|
25
25
|
|
26
26
|
t.timestamps
|
27
|
+
t.index %i[job_class key], unique: true
|
27
28
|
end
|
28
29
|
|
29
30
|
create_table :chrono_forge_execution_logs do |t|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chrono_forge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|