eco-helpers 3.0.18 → 3.0.20

Sign up to get free protection for your applications and to get access to all the features.
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