eco-helpers 3.0.18 → 3.0.20

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +34 -3
  4. data/eco-helpers.gemspec +3 -3
  5. data/lib/eco/api/common/loaders/config/session.rb +12 -0
  6. data/lib/eco/api/common/loaders/config/workflow/mailer.rb +17 -4
  7. data/lib/eco/api/common/loaders/config.rb +10 -2
  8. data/lib/eco/api/common/loaders/parser.rb +10 -0
  9. data/lib/eco/api/common/people/default_parsers/csv_parser.rb +21 -208
  10. data/lib/eco/api/common/people/default_parsers/helpers/expected_headers.rb +206 -0
  11. data/lib/eco/api/common/people/default_parsers/helpers/null_parsing.rb +36 -0
  12. data/lib/eco/api/common/people/default_parsers/helpers.rb +15 -0
  13. data/lib/eco/api/common/people/default_parsers/json_parser.rb +56 -0
  14. data/lib/eco/api/common/people/default_parsers/xls_parser.rb +13 -14
  15. data/lib/eco/api/common/people/default_parsers.rb +2 -0
  16. data/lib/eco/api/common/people/entry_factory.rb +15 -4
  17. data/lib/eco/api/common/session/sftp.rb +5 -0
  18. data/lib/eco/api/custom/mailer.rb +1 -0
  19. data/lib/eco/api/error.rb +4 -0
  20. data/lib/eco/api/session/batch/job.rb +14 -16
  21. data/lib/eco/api/session/batch/jobs.rb +6 -8
  22. data/lib/eco/api/session/batch/launcher/mode_size.rb +5 -2
  23. data/lib/eco/api/session/batch/launcher/retry.rb +6 -1
  24. data/lib/eco/api/session/batch/launcher/status_handling.rb +4 -2
  25. data/lib/eco/api/session/batch/launcher.rb +3 -3
  26. data/lib/eco/api/session/config/api.rb +1 -0
  27. data/lib/eco/api/session/config/apis/one_off.rb +6 -6
  28. data/lib/eco/api/session/config/workflow.rb +16 -3
  29. data/lib/eco/api/session.rb +13 -7
  30. data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +1 -0
  31. data/lib/eco/api/usecases/default/locations/tagtree_upload_case.rb +2 -0
  32. data/lib/eco/api/usecases/default/utils/cli/group_csv_cli.rb +26 -0
  33. data/lib/eco/api/usecases/default/utils/cli/json_to_csv_cli.rb +10 -0
  34. data/lib/eco/api/usecases/default/utils/cli/sort_csv_cli.rb +17 -0
  35. data/lib/eco/api/usecases/default/utils/cli/split_json_cli.rb +15 -0
  36. data/lib/eco/api/usecases/default/utils/group_csv_case.rb +213 -0
  37. data/lib/eco/api/usecases/default/utils/json_to_csv_case.rb +71 -0
  38. data/lib/eco/api/usecases/default/utils/sort_csv_case.rb +127 -0
  39. data/lib/eco/api/usecases/default/utils/split_json_case.rb +224 -0
  40. data/lib/eco/api/usecases/default/utils.rb +4 -0
  41. data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +22 -15
  42. data/lib/eco/api/usecases/ooze_cases/export_register_case.rb +6 -6
  43. data/lib/eco/api/usecases/ooze_samples/helpers/exportable_register.rb +1 -0
  44. data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +1 -1
  45. data/lib/eco/api/usecases/ooze_samples/ooze_run_base_case.rb +8 -5
  46. data/lib/eco/cli_default/workflow.rb +10 -4
  47. data/lib/eco/csv/stream.rb +2 -0
  48. data/lib/eco/csv.rb +3 -2
  49. data/lib/eco/language/methods/delegate_missing.rb +4 -3
  50. data/lib/eco/version.rb +1 -1
  51. metadata +22 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47c28bbf13f50c0f3f895c40c0ad8532d13233e0e2b81faea474bbeb0a1c2308
4
- data.tar.gz: e13091192acd044b667cd20ebe7bcaf5c17da30c1ef83266fefa9ffa90fb8348
3
+ metadata.gz: 96eaee3dd7897d77b22857cf8f812ce6d0639af324565c54f649c1e8068b7fb4
4
+ data.tar.gz: c9af686fe8f01d45eb3ea6a0dc89de276b789dfd6e3f64b515e17107b233b2d3
5
5
  SHA512:
6
- metadata.gz: 02fe482f0c75e0926fe147e1ab3f4b0a0d1b826990bae3ad8ea97fb0aa53a800e1d208fb62e209877c0594b4f32792096967ec1f0528be8c81c3cd8c404c5131
7
- data.tar.gz: 4f9c12554b25c81243eac348f106392a092853c87204922d25cd1f25d287abcc23461df2b0a6a19b3dd3e4624f625e49ce5920ed04000ab6641f26312a38cb0f
6
+ metadata.gz: 66bdc8b7c98b2aa0e60e205ac64b47a302e2cd8dbe1efd1720ad36c04a4f4c1a70b2cbb4d4978c67a17bd0f86893d34b47ff809f95a3e06952c002b872872553
7
+ data.tar.gz: f205d190dc159c0218bc1cafa52f139a6faaa133a01190ae1b189d2b8b6ef2470f49b634169effcf2021023803ec58f1793b8ce4c019db9d1dd025df7bbf5493
data/.gitignore CHANGED
@@ -5,6 +5,7 @@ Gemfile.lock
5
5
  *.gem
6
6
  /.bundle
7
7
  /.vscode
8
+ .solargraph.yml
8
9
  /vendor/bundle
9
10
  /spec/reports/
10
11
  /tmp/
data/CHANGELOG.md CHANGED
@@ -2,10 +2,43 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [3.0.18] - 2024-10-xx
5
+ ## [3.0.20] - 2024-12-xx
6
6
 
7
7
  ### Added
8
8
 
9
+ - Mailer: to specify non-default `space` in the subject, when used.
10
+ - `Eco::API::Common::Loaders::Config`
11
+ - Can call config with params
12
+ - `Eco::API::Common::Loaders::Session`
13
+ - Which switches the configuration to that of the specific environment
14
+
15
+ ### Changed
16
+
17
+ - On failure, `Eco::API::Session::Job#summary` to give precise estimates on what is pending to be run.
18
+
19
+ ### Fixed
20
+
21
+ ## [3.0.19] - 2024-11-21
22
+
23
+ ### Added
24
+
25
+ - Input entries `:json` parser
26
+ - **Cases**:
27
+ - `json-to-csv`
28
+ - `split-json`
29
+ - `sort-csv`
30
+ - `group-csv`
31
+
32
+ ### Changed
33
+
34
+ - Refactored input file parsers (`csv`, `xls`)
35
+ - Upgraded gems:
36
+ - `ecoportal-api`
37
+ - `ecoportal-api-v2`
38
+ - `ecoportal-api-graphql`
39
+
40
+ ## [3.0.18] - 2024-10-28
41
+
9
42
  ### Changed
10
43
 
11
44
  - upgrade `ecoportal-api` gem
@@ -15,8 +48,6 @@ All notable changes to this project will be documented in this file.
15
48
  - **added** auto-swap endpoint from `job` to `batch` on TimeOut errors.
16
49
  - **retries** also on `Ecoportal::API::Errors::StartTimeOut`
17
50
 
18
- ### Fixed
19
-
20
51
  ## [3.0.17] - 2024-10-18
21
52
 
22
53
  ### Added
data/eco-helpers.gemspec CHANGED
@@ -40,9 +40,9 @@ Gem::Specification.new do |spec|
40
40
  spec.add_dependency 'bcrypt_pbkdf', '~> 1.0'
41
41
  spec.add_dependency 'docx', '>= 0.8.0', '< 0.9'
42
42
  spec.add_dependency 'dotenv', '~> 3'
43
- spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.6'
44
- spec.add_dependency 'ecoportal-api-graphql', '~> 0.4', '>= 0.4.2'
45
- spec.add_dependency 'ecoportal-api-v2', '~> 2.0', '>= 2.0.11'
43
+ spec.add_dependency 'ecoportal-api', '~> 0.10', '>= 0.10.7'
44
+ spec.add_dependency 'ecoportal-api-graphql', '~> 0.4', '>= 0.4.3'
45
+ spec.add_dependency 'ecoportal-api-v2', '~> 2.0', '>= 2.0.12'
46
46
  spec.add_dependency 'ed25519', '~> 1.2'
47
47
  spec.add_dependency 'fast_excel', '>= 0.5.0', '< 0.6'
48
48
  spec.add_dependency 'fuzzy_match', '>= 2.1.0', '< 2.2'
@@ -0,0 +1,12 @@
1
+ class Eco::API::Common::Loaders::Session < Eco::API::Common::Loaders::Config
2
+ class << self
3
+ # This is a config tied to the current environment session.
4
+ def config
5
+ return @config if instance_variable_defined?(:@config)
6
+
7
+ @config = super(key: super.apis.active_root_name)
8
+ end
9
+ end
10
+
11
+ delegate_missing_to :workflow
12
+ end
@@ -18,9 +18,8 @@ class Eco::API::Common::Loaders::Workflow::Mailer < Eco::API::Common::Loaders::W
18
18
  next if session.config.dry_run?
19
19
  next unless session.config.run_mode_remote?
20
20
 
21
- # temporary contingency
22
- maybe_error_pages_or_tree_updates = other_case?(io) && error?
23
- next unless some_update?(io) || maybe_error_pages_or_tree_updates
21
+ # temporary contingency: maybe_error_or_tree_updates?
22
+ next unless some_update?(io) || maybe_error_pages_or_tree_updates?(io)
24
23
 
25
24
  subject = base_subject
26
25
 
@@ -47,7 +46,15 @@ class Eco::API::Common::Loaders::Workflow::Mailer < Eco::API::Common::Loaders::W
47
46
  end
48
47
 
49
48
  def base_subject
50
- "#{org_name} (#{active_enviro}) at #{Time.now.iso8601}"
49
+ "#{org_name} (#{active_enviro_desc}) at #{Time.now.iso8601}"
50
+ end
51
+
52
+ def active_enviro_desc
53
+ space_desc = config.active_enviro_space.to_s.strip
54
+ space_desc = '' if space_desc == 'default'
55
+ space_desc = " - space: '#{space_desc}'" unless space_desc.empty?
56
+
57
+ "#{active_enviro}#{space_desc}"
51
58
  end
52
59
 
53
60
  def some_update?(io)
@@ -66,6 +73,11 @@ class Eco::API::Common::Loaders::Workflow::Mailer < Eco::API::Common::Loaders::W
66
73
  !!error
67
74
  end
68
75
 
76
+ # @note at some stage, tree cases should have their own case type
77
+ def maybe_error_pages_or_tree_updates?(io)
78
+ other_case?(io) && error?
79
+ end
80
+
69
81
  def errors_n_warnings(io)
70
82
  [error_message, log_err_n_warn(io)].join("\n")
71
83
  end
@@ -73,6 +85,7 @@ class Eco::API::Common::Loaders::Workflow::Mailer < Eco::API::Common::Loaders::W
73
85
  def log_err_n_warn(io)
74
86
  warn_errors = io.logger.cache.logs(level: %i[error warn])
75
87
  return if warn_errors.empty?
88
+
76
89
  "ALL WARNINGS & ERRORS:\n#{warn_errors.join}\n"
77
90
  end
78
91
 
@@ -1,17 +1,24 @@
1
1
  class Eco::API::Common::Loaders::Config
2
2
  extend Eco::API::Common::ClassHelpers
3
3
  extend Eco::Language::Methods::DelegateMissing
4
+
4
5
  inheritable_class_vars :delegate_missing_to
5
6
 
6
7
  class << self
7
8
  # To create samples of configurations
8
9
  def config_block(&block)
9
10
  return @config_block unless block_given?
11
+
12
+ unless @config_block.nil?
13
+ msg = "Reconfiguring config_block on #{self}"
14
+ session.log(:warn) { msg }
15
+ end
16
+
10
17
  @config_block = block
11
18
  end
12
19
 
13
- def config
14
- ASSETS.config
20
+ def config(...)
21
+ ASSETS.config(...)
15
22
  end
16
23
 
17
24
  def cli
@@ -27,3 +34,4 @@ class Eco::API::Common::Loaders::Config
27
34
  end
28
35
 
29
36
  require_relative 'config/workflow'
37
+ require_relative 'config/session'
@@ -21,6 +21,7 @@ module Eco
21
21
  return [] if miss.empty?
22
22
  return attrs if match.empty?
23
23
  return miss if type == :all
24
+
24
25
  []
25
26
  end
26
27
  end
@@ -38,6 +39,7 @@ module Eco
38
39
  msg << "#{self.class}, is linked to"
39
40
  return @attribute || (raise msg)
40
41
  end
42
+
41
43
  name value
42
44
  @attribute = value
43
45
  end
@@ -62,6 +64,7 @@ module Eco
62
64
  def parsing_phase(phase = nil)
63
65
  @parsing_phase ||= :internal
64
66
  return @parsing_phase unless phase
67
+
65
68
  @parsing_phase = phase
66
69
  end
67
70
 
@@ -71,6 +74,7 @@ module Eco
71
74
  def serializing_phase(phase = nil)
72
75
  @serializing_phase ||= :person
73
76
  return @serializing_phase unless phase
77
+
74
78
  @serializing_phase = phase
75
79
  end
76
80
 
@@ -86,6 +90,7 @@ module Eco
86
90
  # Helper to build the `active_when` condition.
87
91
  def active_when_all(*attrs)
88
92
  @active_when_attrs = RequiredAttrs.new(attribute, :all, attrs)
93
+
89
94
  @active_when = proc do |source_data|
90
95
  keys = data_keys(source_data)
91
96
  attrs.all? {|key| keys.include?(key)}
@@ -149,6 +154,10 @@ module Eco
149
154
 
150
155
  private
151
156
 
157
+ def options
158
+ ASSETS.cli.options
159
+ end
160
+
152
161
  def _define_parser(attr_parser)
153
162
  if (active_when = self.class.active_when)
154
163
  attr_parser.def_parser(
@@ -166,6 +175,7 @@ module Eco
166
175
 
167
176
  def _define_serializer(attr_parser)
168
177
  return unless respond_to?(:serializer, true)
178
+
169
179
  attr_parser.def_serializer(
170
180
  self.class.serializing_phase,
171
181
  &method(:serializer)
@@ -1,235 +1,48 @@
1
1
  class Eco::API::Common::People::DefaultParsers::CSVParser < Eco::API::Common::Loaders::Parser
2
2
  attribute :csv
3
3
 
4
+ include Eco::API::Common::People::DefaultParsers::Helpers::ExpectedHeaders
5
+ include Eco::API::Common::People::DefaultParsers::Helpers::NullParsing
6
+
4
7
  def parser(data, deps)
5
8
  Eco::CSV.parse(data, headers: true, skip_blanks: true).tap do |table|
6
- require_headers!(table)
7
- check_headers(table) if deps[:check_headers] && check_headers?
8
- end.each_with_object([]) do |row, arr_hash|
9
- row_hash = row.headers.uniq.each_with_object({}) do |attr, hash|
9
+ require_headers!(table.headers)
10
+
11
+ next unless deps[:check_headers]
12
+ next unless check_headers?
13
+
14
+ check_headers!(
15
+ table.headers,
16
+ order_check: options.dig(:input, :header_check, :order)
17
+ )
18
+ end.each_with_object([]) do |item, arr_hash|
19
+ item_hash = item.headers.uniq.each_with_object({}) do |attr, hash|
10
20
  next if attr.to_s.strip.empty?
11
- hash[attr.strip] = parse_string(row[attr])
21
+
22
+ hash[attr.strip] = parse_null(item[attr])
12
23
  end
13
- arr_hash.push(row_hash)
24
+
25
+ arr_hash.push(item_hash)
14
26
  end
15
27
  end
16
28
 
17
29
  def serializer(array_hash, _deps)
18
30
  arr_rows = []
31
+
19
32
  unless array_hash.empty?
20
33
  header = array_hash.first.keys
34
+
21
35
  arr_rows = array_hash.map do |csv_row|
22
36
  CSV::Row.new(header, csv_row.values_at(*header))
23
37
  end
24
38
  end
39
+
25
40
  CSV::Table.new(arr_rows).to_csv
26
41
  end
27
42
 
28
43
  private
29
44
 
30
- def abort(msg)
31
- super(msg, raising: false)
32
- end
33
-
34
- def require_headers!(table)
35
- headers = table.headers
36
-
37
- abort("Missing headers in CSV") unless headers&.any?
38
-
39
- empty = []
40
- headers.each_with_index do |header, idx|
41
- empty << idx if header.to_s.strip.empty?
42
- end
43
-
44
- abort("Empty headers in column(s): #{empty.join(', ')}") if empty.any?
45
-
46
- true
47
- end
48
-
49
45
  def check_headers?
50
46
  !options.dig(:input, :header_check, :skip)
51
47
  end
52
-
53
- def options
54
- ASSETS.cli.options
55
- end
56
-
57
- def parse_string(value)
58
- return nil if value.to_s.empty?
59
- return nil if null?(value)
60
- value
61
- end
62
-
63
- def null?(value)
64
- return true unless value
65
-
66
- str = value.strip.upcase
67
- ["NULL"].any? {|token| str == token}
68
- end
69
-
70
- def check_headers(table) # rubocop:disable Metrics/AbcSize
71
- headers = table.headers
72
- unmatch = []
73
- unmatch = unmatched_headers(headers) if options.dig(:input, :header_check, :order)
74
- missing = missing_headers(headers)
75
- unknown = unknown_headers(headers)
76
- criteria = [unknown, missing[:direct], missing[:indirect], unmatch]
77
- return if criteria.all?(&:empty?)
78
-
79
- msg = "Detected possible HEADER ISSUES !!!\n"
80
-
81
- # requires exact match
82
- unless unmatch.empty?
83
- msg << "CSV headers do NOT exactly match the expected:\n"
84
- msg << " * Expected: #{known_headers}\n"
85
- expected, given = unmatch.first
86
- msg << " * First unmatch => Given: '#{given}' where expected '#{expected}'\n"
87
- missed = known_headers - headers
88
- unless missed.empty?
89
- msg << " * Missing headers:\n"
90
- msg << " - #{missed.join("\n - ")}\n"
91
- end
92
- end
93
-
94
- msg << "Missing or Wrong HEADER names in the CSV file:\n"
95
- msg << " * UNKNOWN (or not used?): #{unknown}\n" unless unknown.empty?
96
- msg << " * MISSING HEADER: #{missing[:direct]}\n" unless missing[:direct].empty?
97
-
98
- unless (data = missing[:indirect]).empty?
99
- msg << " * MISSING INDIRECTLY:\n"
100
- data.each do |ext, info|
101
- msg << " - '#{ext}' => "
102
- msg << (info[:attrs] || {}).map do |status, attrs|
103
- if status == :inactive
104
- "makes inactive: #{attrs}"
105
- elsif status == :active
106
- "there could be missing info in: #{attrs}"
107
- end
108
- end.compact.join("; ")
109
- msg << "\n"
110
- end
111
- end
112
-
113
- log(:warn) { msg }
114
-
115
- msg = "There were issues identified on the CSV header names. Aborting..."
116
- abort(msg) if options.dig(:input, :header_check, :must_be_valid)
117
-
118
- sleep(2)
119
- end
120
-
121
- def unmatched_headers(headers)
122
- known_headers.zip(headers).reject do |(expected, given)|
123
- expected == given
124
- end
125
- end
126
-
127
- def unknown_headers(headers)
128
- (headers - known_headers) - all_internal_attrs
129
- end
130
-
131
- def missing_headers(headers) # rubocop:disable Metrics/AbcSize
132
- int_head = internal_present_or_active(headers)
133
- external = headers.select do |e|
134
- i = fields_mapper.to_internal(e)
135
- int_head.include?(i)
136
- end
137
-
138
- ext_present = known_headers_present(int_head) | external
139
- ext_miss = known_headers - ext_present
140
-
141
- {
142
- direct: [],
143
- indirect: {}
144
- }.tap do |missing|
145
- ext_miss.each do |ext|
146
- next unless (int = fields_mapper.to_internal(ext))
147
-
148
- missing[:direct] << ext if all_internal_attrs.include?(int)
149
- related_attrs_requirements = required_attrs.values.select do |req|
150
- dep = req.dependant?(int)
151
- affects = dep && !int_head.include?(int)
152
- in_header = int_head.include?(req.attr)
153
- affects || (dep && !in_header)
154
- end
155
-
156
- next if related_attrs_requirements.empty?
157
-
158
- data = missing[:indirect][ext] = {}
159
- data[:int] = int
160
- data[:attrs] = {}
161
-
162
- related_attrs_requirements.each_with_object(data[:attrs]) do |req, attrs|
163
- status = req.active?(*int_head) ? :active : :inactive
164
- attrs[status] ||= []
165
- attrs[status] << req.attr
166
- end
167
- end
168
- end
169
- end
170
-
171
- def known_headers_present(headers_internal)
172
- known_headers.select do |ext|
173
- int = fields_mapper.to_internal(ext)
174
- headers_internal.include?(int)
175
- end
176
- end
177
-
178
- # Scopes what internal attrs appear in headers as they are
179
- def internal_present_or_active(headers, inactive_requirements = {}) # rubocop:disable Metrics/AbcSize
180
- # internal attrs that are not being mapped
181
- int_all = all_internal_attrs.reject {|i| fields_mapper.external?(i)}
182
- hint = headers & int_all
183
- hext = headers - hint
184
- int_present = hint + hext.map {|e| fields_mapper.to_internal(e)}.compact
185
-
186
- update_inactive = proc do
187
- inactive_requirements.dup.each do |attr, req|
188
- next unless req.active?(*int_present)
189
-
190
- inactive_requirements.delete(attr)
191
- int_present << attr
192
- update_inactive.call
193
- end
194
- end
195
-
196
- required_attrs.each_value do |req|
197
- next if int_present.include?(req)
198
-
199
- if req.active?(*int_present)
200
- inactive_requirements.delete(req.attr)
201
- int_present << req.attr
202
- update_inactive.call
203
- else
204
- inactive_requirements[req.attr] = req
205
- end
206
- end
207
-
208
- int_present
209
- end
210
-
211
- # The csv header names as expected
212
- def known_headers
213
- @known_headers ||= fields_mapper.list(:external).compact.uniq
214
- end
215
-
216
- def fields_mapper
217
- session.fields_mapper
218
- end
219
-
220
- def required_attrs
221
- @required_attrs ||= person_parser.required_attrs.to_h {|ra| [ra.attr, ra]}
222
- end
223
-
224
- def all_internal_attrs
225
- @all_internal_attrs ||= [].tap do |int_attrs|
226
- known_int_attrs = person_parser.all_attrs(include_defined_parsers: true)
227
- known_int_attrs |= fields_mapper.list(:internal).compact
228
- int_attrs.concat(known_int_attrs)
229
- end
230
- end
231
-
232
- def person_parser
233
- session.entry_factory.person_parser
234
- end
235
48
  end