eco-helpers 3.2.2 → 3.2.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +24 -1
  4. data/eco-helpers.gemspec +3 -3
  5. data/lib/eco/api/common/loaders/config/workflow/mailer.rb +1 -0
  6. data/lib/eco/api/common/people/person_entry.rb +1 -1
  7. data/lib/eco/api/common/people/person_parser.rb +1 -0
  8. data/lib/eco/api/organization/tag_tree.rb +11 -4
  9. data/lib/eco/api/usecases/default/locations/tagtree_upload_case.rb +20 -2
  10. data/lib/eco/api/usecases/graphql/helpers/location/base/tree_tracking.rb +14 -7
  11. data/lib/eco/api/usecases/graphql/helpers/location/base.rb +1 -1
  12. data/lib/eco/api/usecases/graphql/helpers/location/command/diff/as_update.rb +0 -1
  13. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_archive.rb +0 -1
  14. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_unarchive.rb +4 -2
  15. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable.rb +1 -0
  16. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable/relation_safe_sort.rb +4 -0
  17. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable.rb +6 -1
  18. data/lib/eco/api/usecases/graphql/helpers/location/command/end_points/optimizations.rb +79 -0
  19. data/lib/eco/api/usecases/graphql/helpers/location/command/end_points.rb +101 -0
  20. data/lib/eco/api/usecases/graphql/helpers/location/command/input_unit_response.rb +69 -0
  21. data/lib/eco/api/usecases/graphql/helpers/location/command/result.rb +11 -10
  22. data/lib/eco/api/usecases/graphql/helpers/location/command/results.rb +126 -64
  23. data/lib/eco/api/usecases/graphql/helpers/location/command.rb +24 -30
  24. data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +203 -38
  25. data/lib/eco/api/usecases/graphql/samples/location/command/results.rb +46 -14
  26. data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +0 -51
  27. data/lib/eco/api/usecases/graphql/samples/location/command/track_changed_ids.rb +6 -14
  28. data/lib/eco/api/usecases/graphql/samples/location/command.rb +1 -1
  29. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/inputable.rb +4 -1
  30. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff.rb +6 -0
  31. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/input.rb +1 -0
  32. data/lib/eco/api/usecases/lib/error_handling.rb +1 -1
  33. data/lib/eco/api/usecases/ooze_samples/helpers/creatable.rb +1 -0
  34. data/lib/eco/api/usecases/ooze_samples/helpers/rescuable.rb +1 -0
  35. data/lib/eco/data/hashes/array_diff.rb +14 -4
  36. data/lib/eco/data/locations/node_base/csv_convert.rb +9 -1
  37. data/lib/eco/data/locations/node_diff/nodes_diff/clustered_treeify.rb +1 -0
  38. data/lib/eco/data/locations/node_diff/nodes_diff.rb +6 -0
  39. data/lib/eco/version.rb +1 -1
  40. metadata +9 -7
  41. data/lib/eco/api/usecases/graphql/helpers/location/command/optimizations.rb +0 -84
@@ -1,63 +1,138 @@
1
1
  module Eco::API::UseCases::GraphQL::Helpers::Location::Command
2
+ # Final ressults of the full run.
2
3
  class Results
3
- attr_reader :input, :response
4
+ attr_accessor :draft
5
+ attr_accessor :target_tree
4
6
 
5
- def initialize(input, response)
6
- @input = input
7
- @response = response
7
+ def initialize(target_tree)
8
+ @target_tree = target_tree
8
9
  end
9
10
 
10
- def stats
11
- msg = ''
12
- msg << " * Errored: #{errored.count} #{first_err_str}\n" if errored?
13
- msg << " * Applied: #{errored.count} #{last_okay_str}\n" if some_applied?
14
- msg << " * Pending: #{pending.count}\n" if some_pending?
15
- msg
11
+ def draft_id
12
+ draft&.id
16
13
  end
17
14
 
18
- # Was this configured to force-continue on command error?
19
- def force?
20
- input[:force]
15
+ def parent_structure
16
+ draft&.parent_structure || target_tree
21
17
  end
22
18
 
23
- # # Offers a summary. If anything went wrong, it's `false`.
24
- # # If everything went right, it's `true`.
25
- # def ok?
26
- # response&.ok
27
- # end
19
+ # @note target parent structure id
20
+ def structure_id
21
+ parent_structure&.id
22
+ end
28
23
 
29
- # Overal errors (i.e. ID clashes between different structures)
30
- def error
31
- response&.error
24
+ # @note the result of the draft
25
+ def structure
26
+ draft&.structure
32
27
  end
33
28
 
29
+ # @note it receives the final `results` response (when the draft gets published)
30
+ def final_response(response = nil)
31
+ return @response if response.nil?
32
+
33
+ @response = response
34
+ end
35
+
36
+ def final_response?
37
+ !final_response.nil?
38
+ end
39
+
40
+ # @note it only accounts for the errors of the final publishing of the draft.
34
41
  def error?
35
- !!error
42
+ return false unless final_response
43
+
44
+ final_response.error?
36
45
  end
37
46
 
38
47
  def success?
39
- !error? && results.all?(&:success?)
48
+ !error?
49
+ end
50
+
51
+ def applied_commands(with_id_change: false)
52
+ results.select(&:applied?).then do |applied|
53
+ next applied unless with_id_change
54
+
55
+ applied.select do |result|
56
+ next false unless (command = result.command_result_data)
57
+
58
+ command.keys.include?(:newId)
59
+ end
60
+ end
40
61
  end
41
62
 
42
63
  def results
43
- @results ||= input_commands.zip(response_results).each_with_object([]) do |(i, r), results|
64
+ return [] unless final_response
65
+
66
+ @results ||= input_commands.zip(command_results).each_with_object([]) do |(i, r), results|
44
67
  results << Result.new(i, r)
45
68
  end
46
69
  end
47
70
 
71
+ # @note this captures the latest draft
72
+ def add_response(key, input_unit_response)
73
+ self[key] << input_unit_response
74
+ input_unit_response.tap do |iur|
75
+ next if final_response
76
+
77
+ self.draft = iur.draft
78
+ end
79
+ end
80
+
81
+ def[](key)
82
+ responses[key] ||= []
83
+ end
84
+
85
+ def responses
86
+ @responses ||= {}
87
+ end
88
+
89
+ def keys
90
+ responses.keys
91
+ end
92
+
48
93
  def count
49
- results.count
94
+ responses.values.flatten.count
95
+ end
96
+
97
+ def input_commands
98
+ keys.each_with_object([]) do |key, commands|
99
+ self[key].each do |input|
100
+ next unless input.is_a?(InputUnitResponse)
101
+
102
+ commands.concat(input.commands)
103
+ end
104
+ end
50
105
  end
51
106
 
52
- def input_result(input)
53
- results_by_input[input]
107
+ def command_results(response = final_response)
108
+ response = final_response(response)
109
+ return [] unless response
110
+
111
+ response.results || []
112
+ end
113
+
114
+ def stats
115
+ msg = ''
116
+ msg << " * Errored: #{errored.count} #{first_err_str}\n" if errored?
117
+ msg << " * Applied: #{applied.count} #{last_okay_str}\n" if some_applied?
118
+ msg << " * Pending: #{pending.count}\n" if some_pending?
119
+ msg
54
120
  end
55
121
 
56
- def input_idx(input)
57
- results.index(input_result(input))
122
+ # Overal errors (i.e. ID clashes between different structures)
123
+ def error
124
+ response&.error
58
125
  end
59
126
 
60
- def idx(result)
127
+ # def input_result(input)
128
+ # results_by_input[input]
129
+ # end
130
+
131
+ # def input_idx(input)
132
+ # results.index(input_result(input))
133
+ # end
134
+
135
+ def result_idx(result)
61
136
  results.index(result)
62
137
  end
63
138
 
@@ -65,28 +140,32 @@ module Eco::API::UseCases::GraphQL::Helpers::Location::Command
65
140
  @errored ||= results.select(&:error?)
66
141
  end
67
142
 
143
+ def applied
144
+ @applied ||= results.select(&:applied?)
145
+ end
146
+
147
+ def pending
148
+ @pending ||= results.select(&:pending?)
149
+ end
150
+
68
151
  def errored?
69
152
  errored.any?
70
153
  end
71
154
 
155
+ def applied?
156
+ results.all?(&:applied?)
157
+ end
158
+
72
159
  def first_errored
73
160
  errored.first
74
161
  end
75
162
 
76
163
  def first_errored_idx
77
- idx(first_errored)
78
- end
79
-
80
- def applied
81
- @applied ||= results.select(&:applied?)
82
- end
83
-
84
- def applied?
85
- results.all?(&:applied?)
164
+ result_idx(first_errored)
86
165
  end
87
166
 
88
167
  def some_applied?
89
- applied.count.positive?
168
+ applied.any?
90
169
  end
91
170
 
92
171
  def last_applied
@@ -94,11 +173,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location::Command
94
173
  end
95
174
 
96
175
  def last_applied_idx
97
- idx(last_applied)
98
- end
99
-
100
- def pending
101
- @pending ||= results.select(&:pending?)
176
+ result_idx(last_applied)
102
177
  end
103
178
 
104
179
  def some_pending?
@@ -107,34 +182,21 @@ module Eco::API::UseCases::GraphQL::Helpers::Location::Command
107
182
 
108
183
  private
109
184
 
110
- def results_by_input
111
- @results_by_input ||= results.each_with_object({}) do |r, h|
112
- h[r.input] = r
113
- end
114
- end
115
-
116
- def input_commands
117
- input[:commands]
118
- end
119
-
120
- def response_results
121
- response&.results || []
122
- end
185
+ # def results_by_input
186
+ # @results_by_input ||= results.each_with_object({}) do |res, h|
187
+ # h[res.input] = res
188
+ # end
189
+ # end
123
190
 
124
191
  # First error is relevant only if request was NOT forced to continue.
125
192
  def first_err_str
126
- return '' if force?
127
-
128
- pre_str = 'stopped on node'
129
- pre_str = 'first error' unless force?
193
+ pre_str = 'first error'
130
194
 
131
195
  "(#{pre_str}: '#{first_errored&.node_id}' - idx: #{first_errored_idx})"
132
196
  end
133
197
 
134
198
  # Last successfully applied is relevant only if request was NOT forced to continue.
135
199
  def last_okay_str
136
- return '' if force?
137
-
138
200
  "(last node done: '#{last_applied&.node_id}' - idx: #{last_applied_idx})"
139
201
  end
140
202
  end
@@ -3,32 +3,15 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
3
3
  include Eco::Language::AuxiliarLogger
4
4
  include Eco::API::UseCases::GraphQL::Helpers::Location::Base
5
5
 
6
- require_relative 'command/optimizations'
7
- include Eco::API::UseCases::GraphQL::Helpers::Location::Command::Optimizations
6
+ require_relative 'command/end_points'
7
+ include Eco::API::UseCases::GraphQL::Helpers::Location::Command::EndPoints
8
8
 
9
- # Actual GraphQL request
10
- def apply_commands(input, &block)
11
- graphql.locationStructure.applyCommands(input: input, &block)
12
- end
13
-
14
- # With given the commands, it generates the input of the endpoint mutation.
15
- # @param commands [Array<Hash>]
16
- def input(commands, force_continue: force_continue?)
17
- {
18
- clientMutationId: '',
19
- id: target_structure_id,
20
- force: force_continue,
21
- commands: commands
22
- }
23
- end
24
-
25
- # @param track_tree_mode [Symbol] when should updates happen
9
+ # @note it does the API call.
26
10
  # @return see #with_sliced_input
27
11
  def sliced_batches(
28
12
  batch_input,
29
- size: commands_per_page,
30
- desc: :input,
31
- track_tree_mode: default_tree_tracking_mode
13
+ size: commands_per_page,
14
+ desc: :input
32
15
  )
33
16
  dry_run_msg = simulate? ? '(dry-run) ' : ''
34
17
 
@@ -46,19 +29,29 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
46
29
  msg << "with #{count} commands (done #{done} of #{total})..."
47
30
  log(:info) { msg }
48
31
 
49
- response = nil
50
32
  unless simulate? && !options.dig(:requests, :backup)
51
- backup(sliced_input, type: "tree_update_#{desc}_request_#{page}_of_#{pages}")
33
+ watermark = "tree_update_#{desc}_response_#{page}_of_#{pages}"
34
+
35
+ backup(sliced_input, type: watermark)
52
36
  end
53
37
 
54
- if simulate?
55
- log(:info) { sliced_input.pretty_inspect } if page < 3
56
- else
57
- curr_block = scope_commands_block(page, pages, track_tree_mode: track_tree_mode)
58
- response = apply_commands(sliced_input, &curr_block)
59
- backup(response, type: "tree_update_#{desc}_response_#{page}_of_#{pages}")
38
+ if simulate? && page < 3
39
+ log(:info) {
40
+ JSON.pretty_generate(sliced_input)
41
+ }
60
42
  end
61
43
 
44
+ # fetch structure? (by using default, when curr_block == nil)
45
+ # We should only fetch at the very end (last standard stage)
46
+ # - We will explicitly fetch the draft structure before re-archive.
47
+ response = add_commands(
48
+ sliced_input,
49
+ &commands_payload_without_structure_block
50
+ )
51
+
52
+ watermark = "tree_update_#{desc}_response_#{page}_of_#{pages}"
53
+ backup(response, type: watermark) unless simulate?
54
+
62
55
  done += count
63
56
  yield(sliced_input, response, page, pages, done, total) if block_given?
64
57
  end
@@ -88,6 +81,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
88
81
  end
89
82
 
90
83
  require_relative 'command/result'
84
+ require_relative 'command/input_unit_response'
91
85
  require_relative 'command/results'
92
86
  require_relative 'command/diff'
93
87
  require_relative 'command/diffs'
@@ -1,9 +1,32 @@
1
1
  class Eco::API::UseCases::GraphQL::Samples::Location
2
2
  module Command::DSL
3
+ include Eco::API::UseCases::GraphQL::Samples::Location::Service::TreeDiff
3
4
  include Eco::API::UseCases::GraphQL::Helpers::Location::Command
4
5
  include Eco::API::UseCases::GraphQL::Samples::Location::Command::TrackChangedIds
5
6
  include Eco::API::UseCases::GraphQL::Samples::Location::Command::Results
6
7
 
8
+ def print_diff_details?
9
+ false
10
+ end
11
+
12
+ # With given the commands, it generates the input of the endpoint mutation.
13
+ # @param commands [Array<Hash>]
14
+ def input(commands, force_continue: force_continue?)
15
+ return unless (commands || []).any?
16
+
17
+ results.draft ||= create_draft(
18
+ current_tree.id,
19
+ &create_payload_without_structure_block
20
+ ).draft
21
+
22
+ {
23
+ clientMutationId: '',
24
+ id: results.draft_id,
25
+ commands: commands
26
+ }
27
+ end
28
+
29
+ # @note this method should be overriden/re-implemented
7
30
  # @example of implementation:
8
31
  # def inputs(command_types, force_continue: force_continue?)
9
32
  # {}.tap do |sequence|
@@ -16,65 +39,207 @@ class Eco::API::UseCases::GraphQL::Samples::Location
16
39
  # end
17
40
  # end
18
41
  # end
19
- def inputs(*_args, force_continue: force_continue?, **_kargs, &_block) # rubocop:disable Lint/UnusedMethodArgument
20
- msg = "You should implement this method in your child class.\n"
21
- msg << 'Which should yield the input Hash and the stage or descriptor.'
22
- raise Eco::API::UseCases::GraphQL::Base::NotImplementedMethod, msg
42
+ def inputs(nodes_diff = comparer, force_continue: force_continue?)
43
+ {}.tap do |sequence|
44
+ unless nodes_diff && nodes_diff.respond_to?(:commands)
45
+ msg = "You should implement this method in your child class.\n"
46
+ msg << "Which should yield the input Hash and the stage or descriptor.\n"
47
+ msg << "Or you should provide the `nodes_diff` object."
48
+ raise Eco::API::UseCases::GraphQL::Base::NotImplementedMethod, msg
49
+ end
50
+
51
+ nodes_diff.commands do |comms, stage|
52
+ sequence[stage] = input(comms, force_continue: force_continue)
53
+ end
54
+ end.tap do |sequence|
55
+ sequence.each do |stage, input|
56
+ yield(input, stage) if block_given?
57
+ end
58
+ end
23
59
  end
24
60
 
25
- # Main processor
26
- def process # rubocop:disable Metrics/AbcSize
27
- self.error = false
61
+ # MAIN PROCESSOR
62
+ def process
63
+ self.error = false
64
+ some_update = false
28
65
 
29
66
  with_error_handling do
30
67
  super if defined?(super)
31
68
 
32
- # this may trigger a backup of the tagtree
69
+ # The very first track of the current structure.
70
+ # - This may trigger a backup of the tagtree.
33
71
  self.current_tree ||= live_tree
72
+ results(current_tree)
34
73
 
35
- inputs(force_continue: force_continue?) do |input, stage|
36
- results[stage] ||= []
74
+ inputs(
75
+ force_continue: force_continue?
76
+ ) do |input, stage|
77
+ next unless input
37
78
 
38
- track_mode = batch_tree_track_mode(stage)
79
+ some_update = true
39
80
 
40
- # yields the result of each batch
41
- sliced_batches(input, desc: stage, track_tree_mode: track_mode) do |sliced_input, response, page, pages, done, total| # rubocop:disable Metrics/ParameterLists, Layout/LineLength
42
- track_current_tree(response&.structure)
81
+ sliced_batches(
82
+ input,
83
+ desc: stage,
84
+ &results_tracking_block(stage: stage)
85
+ )
43
86
 
44
- results[stage] << (page_results = request_results_class.new(sliced_input, response))
45
- update_tags_remap_table(page_results, stage, current_tree)
46
-
47
- self.error = page_errors?(page_results, page, pages, done, total, stage: stage)
48
- break if error
49
- end
50
-
51
- break if error
87
+ break if error?
52
88
  rescue StandardError => err
53
89
  log(:error) { err.patch_full_message }
54
90
  raise
55
91
  end
56
92
  end
57
93
  ensure
58
- rescued { self.tags_remap_csv_file = generate_tags_remap_csv }
59
- rescued { close_handling_tags_remap_csv }
94
+ rescued do
95
+ next if exception
96
+
97
+ refetch_draft if some_update
98
+ rearchive
99
+ end
100
+
101
+ rescued { delete_or_publish_draft }
102
+ rescued { manage_remaps_table }
103
+ end
104
+ end
105
+
106
+ def results_tracking_block(stage:)
107
+ proc do |sliced_input, response, page, pages, done, total|
108
+ # yields the result of each batch
109
+
110
+ input_response = input_unit_response_class.new(
111
+ sliced_input,
112
+ response
113
+ )
114
+
115
+ results.add_response(
116
+ stage,
117
+ input_response
118
+ )
119
+
120
+ # early detection of errors
121
+ self.error ||= page_errors?(
122
+ input_response,
123
+ page,
124
+ pages,
125
+ done,
126
+ total,
127
+ stage: stage
128
+ )
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ # Work with adapted diff builders.
135
+ def nodes_diff_class
136
+ Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
137
+ end
138
+
139
+ # Before closing, run RE-ARCHIVE: those that where unarchived via archivedToken
140
+ # that should remain archived.
141
+ # @note this is an additional necessary step
142
+ def rearchive
143
+ return if exception?
144
+ return unless rearchive_comparer.respond_to?(:stage_commands)
145
+ return unless rearchive_diffs.any?
146
+
147
+ rearchive_commands = rearchive_comparer.stage_commands(:archive)
148
+ return unless rearchive_commands.any?
149
+
150
+ # create draft, just in case it wasn't already created
151
+ results.draft ||= create_draft(
152
+ results.structure_id,
153
+ &create_payload_without_structure_block
154
+ ).draft
155
+
156
+ stage = :rearchive
157
+
158
+ rearchive_comparer.tap do
159
+ archive_input = input(
160
+ rearchive_commands,
161
+ force_continue: true
162
+ )
163
+
164
+ sliced_batches(
165
+ archive_input,
166
+ desc: stage,
167
+ &results_tracking_block(stage: stage)
168
+ )
169
+ rescue StandardError => err
170
+ log(:error) { err.patch_full_message }
171
+ raise
60
172
  end
61
173
  end
62
174
 
63
- # Default tree tacking behaviour
64
- # @note
65
- # 1. This aims to optimize the time run
66
- # 2. Based on update stage, there are differentiated tracking needs
67
- # @return [Symbol] the tracking mode
68
- def batch_tree_track_mode(stage)
69
- case stage
70
- when :unarchive, :archive
71
- :once
72
- when :id, :id_name
73
- :per_request
74
- when :insert, :move
75
- :per_batch
175
+ # @note this is the 2nd and last spot where we do require a current tree structure.
176
+ def rearchive_comparer
177
+ @rearchive_comparer ||= nodes_diff_class.new(
178
+ as_nodes_json(current_tree), # hash_list(current_tree),
179
+ file_nodes_list,
180
+ original_tree: current_tree,
181
+ logger: logger
182
+ )
183
+ end
184
+ alias_method :rearchive_diffs, :rearchive_comparer
185
+
186
+ def refetch_draft
187
+ return unless results.draft
188
+
189
+ results.draft = fetch_draft(
190
+ results.draft_id,
191
+ structure_id: results.structure_id
192
+ )
193
+
194
+ track_current_tree(results.structure)
195
+
196
+ results.draft
197
+ end
198
+
199
+ def delete_or_publish_draft
200
+ return unless results.draft_id
201
+
202
+ if simulate? || error? || exception?
203
+ delete = true
204
+
205
+ if simulate?
206
+ delete = false
207
+ should_delete? { delete = true}
208
+ end
209
+
210
+ delete_draft(results.draft_id) if delete
76
211
  else
77
- default_tree_tracking_mode
212
+ results.final_response(
213
+ publish_draft(
214
+ results.draft_id,
215
+ &publish_payload_without_structure_block
216
+ )
217
+ )
218
+ end
219
+ end
220
+
221
+ def should_delete?
222
+ session.prompt_user(
223
+ 'Should delete temporary draft? (Y/n):',
224
+ default: 'Y',
225
+ timeout: 5
226
+ ) do |response|
227
+ next unless response.upcase.start_with?('Y')
228
+
229
+ yield
78
230
  end
79
231
  end
232
+
233
+ def manage_remaps_table
234
+ return unless results.final_response?
235
+
236
+ rescued do
237
+ results.applied_commands(with_id_change: true) do |result|
238
+ update_tags_remap_table(result.command)
239
+ end
240
+ end
241
+
242
+ rescued { self.tags_remap_csv_file = generate_tags_remap_csv }
243
+ rescued { close_handling_tags_remap_csv }
244
+ end
80
245
  end