ductwork 0.9.0 → 0.9.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/CHANGELOG.md +4 -0
- data/lib/ductwork/dsl/branch_builder.rb +43 -44
- data/lib/ductwork/dsl/definition_builder.rb +23 -18
- data/lib/ductwork/models/pipeline.rb +28 -26
- data/lib/ductwork/models/step.rb +1 -0
- data/lib/ductwork/version.rb +1 -1
- data/lib/generators/ductwork/install/templates/db/create_ductwork_steps.rb +4 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 816c6bd3f6f983b777f6826e0bd9365be8b5fae5dc3512c2249d1f728208f5d2
|
|
4
|
+
data.tar.gz: 9deca63fadc27613ba93ef45d68ac1e8629b6f2b06bfb931842d59116f8d002c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 746d2583354152bd51dc2acdd1958ec23234d27abd2b4ded448b69e82c164ae8f7c24ae1c91c536d9f8b60a72af826aa8dad0ff5c9b9301b34e6eeae14bf758c
|
|
7
|
+
data.tar.gz: a1114da01143e85ac0b58e5127f1c60cda5a625067e2c36389eee3ee61f30364aad7bf6dd4995ce86c38c05fe0a44a176a1bdd23f1943f5503ea9c6c80d3b0bb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Ductwork Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.1]
|
|
4
|
+
|
|
5
|
+
- fix!: assign unique node names (klass name and stage) in pipeline definition DAG - this is a BREAKING CHANGE because the pipeline definition structure has changed
|
|
6
|
+
|
|
3
7
|
## [0.9.0]
|
|
4
8
|
|
|
5
9
|
- feat: add health check to job worker runner process - this is a basic check if a thread is healthy via `Thread#alive?` and restarts the thread if it is dead
|
|
@@ -7,36 +7,37 @@ module Ductwork
|
|
|
7
7
|
|
|
8
8
|
attr_reader :last_node
|
|
9
9
|
|
|
10
|
-
def initialize(klass:, definition:)
|
|
11
|
-
@last_node = klass.name
|
|
10
|
+
def initialize(klass:, definition:, stages:)
|
|
11
|
+
@last_node = "#{klass.name}.#{stages.length - 1}"
|
|
12
12
|
@definition = definition
|
|
13
|
+
@stages = stages
|
|
13
14
|
@expansions = 0
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def chain(next_klass)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@last_node = next_klass.name
|
|
18
|
+
next_klass_name = "#{next_klass.name}.#{stages.length}"
|
|
19
|
+
definition[:edges][last_node][:to] = [next_klass_name]
|
|
20
|
+
definition[:edges][last_node][:type] = :chain
|
|
21
|
+
definition[:nodes].push(next_klass_name)
|
|
22
|
+
definition[:edges][next_klass_name] ||= { klass: next_klass.name }
|
|
23
|
+
stages.push(1)
|
|
24
|
+
@last_node = next_klass_name
|
|
25
25
|
|
|
26
26
|
self
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def divide(to:)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
def divide(to:) # rubocop:todo Metrics/AbcSize
|
|
30
|
+
next_klass_names = to.map { |klass| "#{klass.name}.#{stages.length}" }
|
|
31
|
+
definition[:edges][last_node][:to] = next_klass_names
|
|
32
|
+
definition[:edges][last_node][:type] = :divide
|
|
33
|
+
definition[:nodes].push(*next_klass_names)
|
|
34
|
+
stages.push(1)
|
|
34
35
|
|
|
35
|
-
definition[:nodes].push(*to.map(&:name))
|
|
36
36
|
sub_branches = to.map do |klass|
|
|
37
|
-
|
|
37
|
+
next_klass_name = "#{klass.name}.#{stages.length - 1}"
|
|
38
|
+
definition[:edges][next_klass_name] ||= { klass: klass.name }
|
|
38
39
|
|
|
39
|
-
Ductwork::DSL::BranchBuilder.new(klass:, definition:)
|
|
40
|
+
Ductwork::DSL::BranchBuilder.new(klass:, definition:, stages:)
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
yield sub_branches
|
|
@@ -44,32 +45,30 @@ module Ductwork
|
|
|
44
45
|
self
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
def combine(*branch_builders, into:)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
def combine(*branch_builders, into:) # rubocop:todo Metrics/AbcSize
|
|
49
|
+
next_klass_name = "#{into.name}.#{stages.length}"
|
|
50
|
+
definition[:edges][last_node][:to] = [next_klass_name]
|
|
51
|
+
definition[:edges][last_node][:type] = :combine
|
|
52
|
+
|
|
52
53
|
branch_builders.each do |branch|
|
|
53
|
-
definition[:edges][branch.last_node]
|
|
54
|
-
|
|
55
|
-
type: :combine,
|
|
56
|
-
}
|
|
54
|
+
definition[:edges][branch.last_node][:to] = [next_klass_name]
|
|
55
|
+
definition[:edges][branch.last_node][:type] = :combine
|
|
57
56
|
end
|
|
58
|
-
definition[:nodes].push(
|
|
59
|
-
definition[:edges][into.name
|
|
57
|
+
definition[:nodes].push(next_klass_name)
|
|
58
|
+
definition[:edges][next_klass_name] ||= { klass: into.name }
|
|
59
|
+
stages.push(1)
|
|
60
60
|
|
|
61
61
|
self
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def expand(to:)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@last_node = to.name
|
|
65
|
+
next_klass_name = "#{to.name}.#{stages.length}"
|
|
66
|
+
definition[:edges][last_node][:to] = [next_klass_name]
|
|
67
|
+
definition[:edges][last_node][:type] = :expand
|
|
68
|
+
definition[:nodes].push(next_klass_name)
|
|
69
|
+
definition[:edges][next_klass_name] ||= { klass: to.name }
|
|
70
|
+
stages.push(1)
|
|
71
|
+
@last_node = next_klass_name
|
|
73
72
|
@expansions += 1
|
|
74
73
|
|
|
75
74
|
self
|
|
@@ -81,13 +80,13 @@ module Ductwork
|
|
|
81
80
|
"Must expand pipeline definition before collapsing steps"
|
|
82
81
|
end
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
83
|
+
next_klass_name = "#{into.name}.#{stages.length}"
|
|
84
|
+
definition[:edges][last_node][:to] = [next_klass_name]
|
|
85
|
+
definition[:edges][last_node][:type] = :collapse
|
|
88
86
|
|
|
89
|
-
definition[:nodes].push(
|
|
90
|
-
definition[:edges][into.name
|
|
87
|
+
definition[:nodes].push(next_klass_name)
|
|
88
|
+
definition[:edges][next_klass_name] ||= { klass: into.name }
|
|
89
|
+
stages.push(1)
|
|
91
90
|
@last_node = into.name
|
|
92
91
|
@expansions -= 1
|
|
93
92
|
|
|
@@ -96,7 +95,7 @@ module Ductwork
|
|
|
96
95
|
|
|
97
96
|
private
|
|
98
97
|
|
|
99
|
-
attr_reader :definition, :expansions
|
|
98
|
+
attr_reader :definition, :expansions, :stages
|
|
100
99
|
end
|
|
101
100
|
end
|
|
102
101
|
end
|
|
@@ -15,23 +15,24 @@ module Ductwork
|
|
|
15
15
|
}
|
|
16
16
|
@divergences = []
|
|
17
17
|
@last_nodes = []
|
|
18
|
+
@stages = []
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def start(klass)
|
|
21
22
|
validate_classes!(klass)
|
|
22
23
|
validate_start_once!
|
|
23
24
|
add_new_nodes(klass)
|
|
25
|
+
increment_position
|
|
24
26
|
|
|
25
27
|
self
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
# NOTE: there is a bug here that does not allow the user to reuse step
|
|
29
|
-
# classes in the same pipeline. i'll fix this later
|
|
30
30
|
def chain(klass)
|
|
31
31
|
validate_classes!(klass)
|
|
32
32
|
validate_definition_started!(action: "chaining")
|
|
33
33
|
add_edge_to_last_nodes(klass, type: :chain)
|
|
34
34
|
add_new_nodes(klass)
|
|
35
|
+
increment_position
|
|
35
36
|
|
|
36
37
|
self
|
|
37
38
|
end
|
|
@@ -41,13 +42,13 @@ module Ductwork
|
|
|
41
42
|
validate_definition_started!(action: "dividing chain")
|
|
42
43
|
add_edge_to_last_nodes(*to, type: :divide)
|
|
43
44
|
add_new_nodes(*to)
|
|
44
|
-
|
|
45
|
+
increment_position
|
|
45
46
|
divergences.push(:divide)
|
|
46
47
|
|
|
47
48
|
if block_given?
|
|
48
49
|
branches = to.map do |klass|
|
|
49
50
|
Ductwork::DSL::BranchBuilder
|
|
50
|
-
.new(klass:, definition:)
|
|
51
|
+
.new(klass:, definition:, stages:)
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
yield branches
|
|
@@ -64,15 +65,14 @@ module Ductwork
|
|
|
64
65
|
divergences.pop
|
|
65
66
|
|
|
66
67
|
last_nodes = definition[:nodes].reverse.select do |node|
|
|
67
|
-
definition
|
|
68
|
+
definition.dig(:edges, node, :to).blank?
|
|
68
69
|
end
|
|
69
70
|
last_nodes.each do |node|
|
|
70
|
-
definition[:edges][node]
|
|
71
|
-
|
|
72
|
-
type: :combine,
|
|
73
|
-
}
|
|
71
|
+
definition[:edges][node][:to] = ["#{into.name}.#{stages.length}"]
|
|
72
|
+
definition[:edges][node][:type] = :combine
|
|
74
73
|
end
|
|
75
74
|
add_new_nodes(into)
|
|
75
|
+
increment_position
|
|
76
76
|
|
|
77
77
|
self
|
|
78
78
|
end
|
|
@@ -82,7 +82,7 @@ module Ductwork
|
|
|
82
82
|
validate_definition_started!(action: "expanding chain")
|
|
83
83
|
add_edge_to_last_nodes(to, type: :expand)
|
|
84
84
|
add_new_nodes(to)
|
|
85
|
-
|
|
85
|
+
increment_position
|
|
86
86
|
divergences.push(:expand)
|
|
87
87
|
|
|
88
88
|
self
|
|
@@ -94,7 +94,7 @@ module Ductwork
|
|
|
94
94
|
validate_can_collapse!
|
|
95
95
|
add_edge_to_last_nodes(into, type: :collapse)
|
|
96
96
|
add_new_nodes(into)
|
|
97
|
-
|
|
97
|
+
increment_position
|
|
98
98
|
divergences.pop
|
|
99
99
|
|
|
100
100
|
self
|
|
@@ -116,7 +116,7 @@ module Ductwork
|
|
|
116
116
|
|
|
117
117
|
private
|
|
118
118
|
|
|
119
|
-
attr_reader :definition, :last_nodes, :divergences
|
|
119
|
+
attr_reader :definition, :last_nodes, :divergences, :stages
|
|
120
120
|
|
|
121
121
|
def validate_classes!(klasses)
|
|
122
122
|
valid = Array(klasses).all? do |klass|
|
|
@@ -165,23 +165,28 @@ module Ductwork
|
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
def add_new_nodes(*klasses)
|
|
168
|
-
nodes = klasses.map
|
|
168
|
+
nodes = klasses.map { |klass| "#{klass.name}.#{stages.length}" }
|
|
169
169
|
@last_nodes = Array(nodes)
|
|
170
170
|
|
|
171
171
|
definition[:nodes].push(*nodes)
|
|
172
172
|
klasses.each do |klass|
|
|
173
|
-
|
|
173
|
+
node = "#{klass.name}.#{stages.length}"
|
|
174
|
+
definition[:edges][node] ||= { klass: klass.name }
|
|
174
175
|
end
|
|
175
176
|
end
|
|
176
177
|
|
|
177
178
|
def add_edge_to_last_nodes(*klasses, type:)
|
|
178
179
|
last_nodes.each do |last_node|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
to = klasses.map { |klass| "#{klass.name}.#{stages.length}" }
|
|
181
|
+
definition[:edges][last_node][:to] = to
|
|
182
|
+
definition[:edges][last_node][:type] = type
|
|
183
|
+
definition[:edges][last_node][:klass] ||= klass
|
|
183
184
|
end
|
|
184
185
|
end
|
|
186
|
+
|
|
187
|
+
def increment_position
|
|
188
|
+
stages.push(1)
|
|
189
|
+
end
|
|
185
190
|
end
|
|
186
191
|
end
|
|
187
192
|
end
|
|
@@ -56,7 +56,8 @@ module Ductwork
|
|
|
56
56
|
raise DefinitionError, "Pipeline must be defined before triggering"
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
node = pipeline_definition.dig(:nodes, 0)
|
|
60
|
+
klass = pipeline_definition.dig(:edges, node, :klass)
|
|
60
61
|
definition = JSON.dump(pipeline_definition)
|
|
61
62
|
|
|
62
63
|
pipeline = Record.transaction do
|
|
@@ -70,7 +71,8 @@ module Ductwork
|
|
|
70
71
|
last_advanced_at: Time.current
|
|
71
72
|
)
|
|
72
73
|
step = p.steps.create!(
|
|
73
|
-
|
|
74
|
+
node: node,
|
|
75
|
+
klass: klass,
|
|
74
76
|
status: :in_progress,
|
|
75
77
|
to_transition: :start,
|
|
76
78
|
started_at: Time.current
|
|
@@ -95,12 +97,12 @@ module Ductwork
|
|
|
95
97
|
# advancing records which may cause memory issues. something to
|
|
96
98
|
# watch out for here and maybe add in config to use AR relation
|
|
97
99
|
# at certain counts or even memory limits.
|
|
98
|
-
advancing_steps = steps.advancing.pluck(:id, :klass)
|
|
100
|
+
advancing_steps = steps.advancing.pluck(:id, :node, :klass)
|
|
99
101
|
advancing_ids = advancing_steps.map(&:first)
|
|
100
102
|
edges = find_edges(advancing_steps)
|
|
101
103
|
|
|
102
104
|
Ductwork::Record.transaction do
|
|
103
|
-
if edges.nil? || edges.
|
|
105
|
+
if edges.nil? || edges.all? { |_, attrs| attrs[:to].blank? }
|
|
104
106
|
conditionally_complete_pipeline(advancing_ids)
|
|
105
107
|
else
|
|
106
108
|
advance_to_next_steps_by_type(edges, advancing_ids)
|
|
@@ -114,23 +116,24 @@ module Ductwork
|
|
|
114
116
|
|
|
115
117
|
private
|
|
116
118
|
|
|
117
|
-
def create_step_and_enqueue_job(edge:, input_arg:,
|
|
119
|
+
def create_step_and_enqueue_job(edge:, input_arg:, node: nil)
|
|
118
120
|
status = :in_progress
|
|
119
121
|
started_at = Time.current
|
|
120
122
|
# NOTE: "chain" is used by ActiveRecord so we have to call
|
|
121
123
|
# this enum value "default" :sad:
|
|
122
124
|
to_transition = edge[:type] == "chain" ? "default" : edge[:type]
|
|
123
|
-
|
|
125
|
+
node ||= edge[:to].sole
|
|
126
|
+
klass = parsed_definition.dig(:edges, node, :klass)
|
|
124
127
|
|
|
125
|
-
next_step = steps.create!(klass:, status:, to_transition:, started_at:)
|
|
128
|
+
next_step = steps.create!(node:, klass:, status:, to_transition:, started_at:)
|
|
126
129
|
Ductwork::Job.enqueue(next_step, input_arg)
|
|
127
130
|
end
|
|
128
131
|
|
|
129
132
|
def find_edges(advancing_steps)
|
|
130
133
|
if advancing_steps.any?
|
|
131
|
-
|
|
134
|
+
nodes = advancing_steps.map(&:second)
|
|
132
135
|
|
|
133
|
-
parsed_definition.fetch(:edges, {}).select { |k| k.in?(
|
|
136
|
+
parsed_definition.fetch(:edges, {}).select { |k| k.in?(nodes) }
|
|
134
137
|
end
|
|
135
138
|
end
|
|
136
139
|
|
|
@@ -158,26 +161,25 @@ module Ductwork
|
|
|
158
161
|
def advance_to_next_steps_by_type(edges, advancing_ids)
|
|
159
162
|
steps.where(id: advancing_ids).update_all(status: :completed, completed_at: Time.current)
|
|
160
163
|
|
|
161
|
-
if edges.all? { |_,
|
|
164
|
+
if edges.all? { |_, attrs| attrs[:type] == "combine" }
|
|
162
165
|
conditionally_combine_next_steps(edges, advancing_ids)
|
|
163
166
|
else
|
|
164
|
-
edges.each do |
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if edge[:type] == "collapse"
|
|
168
|
-
conditionally_collapse_next_steps(step_klass, edge, advancing_ids)
|
|
167
|
+
edges.each do |node, attrs|
|
|
168
|
+
if attrs[:type] == "collapse"
|
|
169
|
+
conditionally_collapse_next_steps(node, attrs, advancing_ids)
|
|
169
170
|
else
|
|
170
|
-
advance_non_merging_steps(
|
|
171
|
+
advance_non_merging_steps(attrs, advancing_ids)
|
|
171
172
|
end
|
|
172
173
|
end
|
|
173
174
|
end
|
|
174
175
|
log_pipeline_advanced(edges)
|
|
175
176
|
end
|
|
176
177
|
|
|
177
|
-
def advance_non_merging_steps(
|
|
178
|
+
def advance_non_merging_steps(edge, advancing_ids)
|
|
178
179
|
to_transition = edge[:type]
|
|
180
|
+
klass = edge[:klass]
|
|
179
181
|
|
|
180
|
-
steps.where(id: advancing_ids, klass:
|
|
182
|
+
steps.where(id: advancing_ids, klass: klass).find_each do |step|
|
|
181
183
|
if to_transition.in?(%w[chain divide])
|
|
182
184
|
advance_to_next_steps(step.id, edge)
|
|
183
185
|
elsif to_transition == "expand"
|
|
@@ -205,15 +207,15 @@ module Ductwork
|
|
|
205
207
|
if too_many
|
|
206
208
|
halted!
|
|
207
209
|
else
|
|
208
|
-
edge[:to].each do |
|
|
210
|
+
edge[:to].each do |node|
|
|
209
211
|
input_arg = Ductwork::Job.find_by(step_id:).return_value
|
|
210
|
-
create_step_and_enqueue_job(edge:, input_arg:,
|
|
212
|
+
create_step_and_enqueue_job(edge:, input_arg:, node:)
|
|
211
213
|
end
|
|
212
214
|
end
|
|
213
215
|
end
|
|
214
216
|
|
|
215
217
|
def conditionally_combine_next_steps(edges, advancing_ids)
|
|
216
|
-
if steps.where(status: %w[pending in_progress],
|
|
218
|
+
if steps.where(status: %w[pending in_progress], node: edges.keys).none?
|
|
217
219
|
combine_next_steps(edges, advancing_ids)
|
|
218
220
|
else
|
|
219
221
|
Ductwork.logger.debug(
|
|
@@ -225,13 +227,13 @@ module Ductwork
|
|
|
225
227
|
end
|
|
226
228
|
|
|
227
229
|
def combine_next_steps(edges, advancing_ids)
|
|
228
|
-
edge = edges.values.sample
|
|
230
|
+
edge = edges.values.sample
|
|
229
231
|
groups = steps
|
|
230
232
|
.where(id: advancing_ids)
|
|
231
|
-
.group(:
|
|
233
|
+
.group(:node)
|
|
232
234
|
.count
|
|
233
235
|
.keys
|
|
234
|
-
.map { |
|
|
236
|
+
.map { |node| steps.where(id: advancing_ids).where(node:) }
|
|
235
237
|
|
|
236
238
|
groups.first.zip(*groups[1..]).each do |group|
|
|
237
239
|
input_arg = Ductwork::Job
|
|
@@ -257,8 +259,8 @@ module Ductwork
|
|
|
257
259
|
end
|
|
258
260
|
end
|
|
259
261
|
|
|
260
|
-
def conditionally_collapse_next_steps(
|
|
261
|
-
if steps.where(status: %w[pending in_progress],
|
|
262
|
+
def conditionally_collapse_next_steps(node, edge, advancing_ids)
|
|
263
|
+
if steps.where(status: %w[pending in_progress], node: node).none?
|
|
262
264
|
collapse_next_steps(edge, advancing_ids)
|
|
263
265
|
else
|
|
264
266
|
Ductwork.logger.debug(
|
data/lib/ductwork/models/step.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Ductwork
|
|
|
5
5
|
belongs_to :pipeline, class_name: "Ductwork::Pipeline"
|
|
6
6
|
has_one :job, class_name: "Ductwork::Job", foreign_key: "step_id", dependent: :destroy
|
|
7
7
|
|
|
8
|
+
validates :node, presence: true
|
|
8
9
|
validates :klass, presence: true
|
|
9
10
|
validates :status, presence: true
|
|
10
11
|
validates :to_transition, presence: true
|
data/lib/ductwork/version.rb
CHANGED
|
@@ -4,6 +4,7 @@ class CreateDuctworkSteps < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>
|
|
|
4
4
|
def change
|
|
5
5
|
create_table :ductwork_steps do |table|
|
|
6
6
|
table.belongs_to :pipeline, index: true, null: false, foreign_key: { to_table: :ductwork_pipelines }
|
|
7
|
+
table.string :node, null: false
|
|
7
8
|
table.string :klass, null: false
|
|
8
9
|
table.string :to_transition, null: false
|
|
9
10
|
table.timestamp :started_at
|
|
@@ -14,9 +15,9 @@ class CreateDuctworkSteps < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>
|
|
|
14
15
|
table.timestamps null: false
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
add_index :ductwork_steps, %i[pipeline_id status
|
|
18
|
-
add_index :ductwork_steps, %i[pipeline_id
|
|
19
|
-
add_index :ductwork_steps, %i[status
|
|
18
|
+
add_index :ductwork_steps, %i[pipeline_id status node]
|
|
19
|
+
add_index :ductwork_steps, %i[pipeline_id node status]
|
|
20
|
+
add_index :ductwork_steps, %i[status node]
|
|
20
21
|
add_index :ductwork_steps, %i[pipeline_id status]
|
|
21
22
|
end
|
|
22
23
|
end
|