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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +34 -3
- data/eco-helpers.gemspec +3 -3
- data/lib/eco/api/common/loaders/config/session.rb +12 -0
- data/lib/eco/api/common/loaders/config/workflow/mailer.rb +17 -4
- data/lib/eco/api/common/loaders/config.rb +10 -2
- data/lib/eco/api/common/loaders/parser.rb +10 -0
- data/lib/eco/api/common/people/default_parsers/csv_parser.rb +21 -208
- data/lib/eco/api/common/people/default_parsers/helpers/expected_headers.rb +206 -0
- data/lib/eco/api/common/people/default_parsers/helpers/null_parsing.rb +36 -0
- data/lib/eco/api/common/people/default_parsers/helpers.rb +15 -0
- data/lib/eco/api/common/people/default_parsers/json_parser.rb +56 -0
- data/lib/eco/api/common/people/default_parsers/xls_parser.rb +13 -14
- data/lib/eco/api/common/people/default_parsers.rb +2 -0
- data/lib/eco/api/common/people/entry_factory.rb +15 -4
- data/lib/eco/api/common/session/sftp.rb +5 -0
- data/lib/eco/api/custom/mailer.rb +1 -0
- data/lib/eco/api/error.rb +4 -0
- data/lib/eco/api/session/batch/job.rb +14 -16
- data/lib/eco/api/session/batch/jobs.rb +6 -8
- data/lib/eco/api/session/batch/launcher/mode_size.rb +5 -2
- data/lib/eco/api/session/batch/launcher/retry.rb +6 -1
- data/lib/eco/api/session/batch/launcher/status_handling.rb +4 -2
- data/lib/eco/api/session/batch/launcher.rb +3 -3
- data/lib/eco/api/session/config/api.rb +1 -0
- data/lib/eco/api/session/config/apis/one_off.rb +6 -6
- data/lib/eco/api/session/config/workflow.rb +16 -3
- data/lib/eco/api/session.rb +13 -7
- data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +1 -0
- data/lib/eco/api/usecases/default/locations/tagtree_upload_case.rb +2 -0
- data/lib/eco/api/usecases/default/utils/cli/group_csv_cli.rb +26 -0
- data/lib/eco/api/usecases/default/utils/cli/json_to_csv_cli.rb +10 -0
- data/lib/eco/api/usecases/default/utils/cli/sort_csv_cli.rb +17 -0
- data/lib/eco/api/usecases/default/utils/cli/split_json_cli.rb +15 -0
- data/lib/eco/api/usecases/default/utils/group_csv_case.rb +213 -0
- data/lib/eco/api/usecases/default/utils/json_to_csv_case.rb +71 -0
- data/lib/eco/api/usecases/default/utils/sort_csv_case.rb +127 -0
- data/lib/eco/api/usecases/default/utils/split_json_case.rb +224 -0
- data/lib/eco/api/usecases/default/utils.rb +4 -0
- data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +22 -15
- data/lib/eco/api/usecases/ooze_cases/export_register_case.rb +6 -6
- data/lib/eco/api/usecases/ooze_samples/helpers/exportable_register.rb +1 -0
- data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +1 -1
- data/lib/eco/api/usecases/ooze_samples/ooze_run_base_case.rb +8 -5
- data/lib/eco/cli_default/workflow.rb +10 -4
- data/lib/eco/csv/stream.rb +2 -0
- data/lib/eco/csv.rb +3 -2
- data/lib/eco/language/methods/delegate_missing.rb +4 -3
- data/lib/eco/version.rb +1 -1
- metadata +22 -9
@@ -0,0 +1,206 @@
|
|
1
|
+
module Eco::API::Common::People
|
2
|
+
class DefaultParsers
|
3
|
+
module Helpers
|
4
|
+
module ExpectedHeaders
|
5
|
+
include Eco::Language::AuxiliarLogger
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def require_headers!(raw_headers)
|
10
|
+
abort("Missing headers in CSV") unless raw_headers&.any?
|
11
|
+
|
12
|
+
empty = []
|
13
|
+
raw_headers.each_with_index do |header, idx|
|
14
|
+
empty << idx if header.to_s.strip.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
abort("Empty headers in column(s): #{empty.join(', ')}") if empty.any?
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_headers!(raw_headers, order_check: false) # rubocop:disable Metrics/AbcSize
|
23
|
+
unmatch = []
|
24
|
+
unmatch = unmatched_headers(raw_headers) if order_check
|
25
|
+
missing = missing_headers(raw_headers)
|
26
|
+
unknown = unknown_headers(raw_headers)
|
27
|
+
|
28
|
+
criteria = [unknown, missing[:direct], missing[:indirect], unmatch]
|
29
|
+
return if criteria.all?(&:empty?)
|
30
|
+
|
31
|
+
msg = "Detected possible HEADER / FIELD ISSUES !!!\n"
|
32
|
+
|
33
|
+
# requires exact match
|
34
|
+
unless unmatch.empty?
|
35
|
+
msg << "File headers/fields do NOT exactly match the expected:\n"
|
36
|
+
msg << " * Expected: #{expected_headers}\n"
|
37
|
+
|
38
|
+
expected, given = unmatch.first
|
39
|
+
msg << " * First unmatch => Given: '#{given}' where expected '#{expected}'\n"
|
40
|
+
|
41
|
+
missed = expected_headers - raw_headers
|
42
|
+
|
43
|
+
unless missed.empty?
|
44
|
+
msg << " * Missing headers/fields:\n"
|
45
|
+
msg << " - #{missed.join("\n - ")}\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
msg << "Missing or Wrong HEADER names in the file:\n"
|
50
|
+
msg << " * UNKNOWN (or not used?): #{unknown}\n" unless unknown.empty?
|
51
|
+
msg << " * MISSING HEADER/FIELD: #{missing[:direct]}\n" unless missing[:direct].empty?
|
52
|
+
|
53
|
+
unless (data = missing[:indirect]).empty?
|
54
|
+
msg << " * MISSING INDIRECTLY:\n"
|
55
|
+
|
56
|
+
data.each do |ext, info|
|
57
|
+
msg << " - '#{ext}' => "
|
58
|
+
msg << (info[:attrs] || {}).map do |status, attrs|
|
59
|
+
if status == :inactive
|
60
|
+
"makes inactive: #{attrs}"
|
61
|
+
elsif status == :active
|
62
|
+
"there could be missing info in: #{attrs}"
|
63
|
+
end
|
64
|
+
end.compact.join("; ")
|
65
|
+
|
66
|
+
msg << "\n"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
log(:warn) { msg }
|
71
|
+
|
72
|
+
msg = "There were issues identified on the file header/field names. Aborting..."
|
73
|
+
abort(msg) if options.dig(:input, :header_check, :must_be_valid)
|
74
|
+
|
75
|
+
sleep(2)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# @return [Hash] with missing `:direct` and `:indirect` attrs, where
|
80
|
+
# - `:direct` [Array] refers to direct attrs
|
81
|
+
# - `:indirect` [Hash] refers to indirect attrs that are `:active` or `:inactive`.
|
82
|
+
def missing_headers(raw_headers) # rubocop:disable Metrics/AbcSize
|
83
|
+
int_head = internal_present_or_active(raw_headers)
|
84
|
+
|
85
|
+
external = raw_headers.select do |e|
|
86
|
+
i = fields_mapper.to_internal(e)
|
87
|
+
int_head.include?(i)
|
88
|
+
end
|
89
|
+
|
90
|
+
ext_present = present_internal_expected_headers(int_head) | external
|
91
|
+
ext_miss = expected_headers - ext_present
|
92
|
+
|
93
|
+
{
|
94
|
+
direct: [],
|
95
|
+
indirect: {}
|
96
|
+
}.tap do |missing|
|
97
|
+
ext_miss.each do |ext|
|
98
|
+
next unless (int = fields_mapper.to_internal(ext))
|
99
|
+
|
100
|
+
missing[:direct] << ext if all_internal_attrs.include?(int)
|
101
|
+
|
102
|
+
related_attrs_requirements = required_attrs.values.select do |req|
|
103
|
+
dep = req.dependant?(int)
|
104
|
+
affects = dep && !int_head.include?(int)
|
105
|
+
in_header = int_head.include?(req.attr)
|
106
|
+
affects || (dep && !in_header)
|
107
|
+
end
|
108
|
+
|
109
|
+
next if related_attrs_requirements.empty?
|
110
|
+
|
111
|
+
data = missing[:indirect][ext] = {}
|
112
|
+
data[:int] = int
|
113
|
+
data[:attrs] = {}
|
114
|
+
|
115
|
+
related_attrs_requirements.each_with_object(data[:attrs]) do |req, attrs|
|
116
|
+
status = req.active?(*int_head) ? :active : :inactive
|
117
|
+
attrs[status] ||= []
|
118
|
+
attrs[status] << req.attr
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
# The input file header names as expected
|
126
|
+
def expected_headers
|
127
|
+
@expected_headers ||= fields_mapper.list(:external).compact.uniq
|
128
|
+
end
|
129
|
+
|
130
|
+
def present_internal_expected_headers(internal_headers)
|
131
|
+
expected_headers.select do |ext|
|
132
|
+
int = fields_mapper.to_internal(ext)
|
133
|
+
internal_headers.include?(int)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def unmatched_headers(raw_headers)
|
138
|
+
expected_headers.zip(raw_headers).reject do |(expected, given)|
|
139
|
+
expected == given
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def unknown_headers(raw_headers)
|
144
|
+
(raw_headers - expected_headers) - all_internal_attrs
|
145
|
+
end
|
146
|
+
|
147
|
+
def fields_mapper
|
148
|
+
session.fields_mapper
|
149
|
+
end
|
150
|
+
|
151
|
+
def required_attrs
|
152
|
+
@required_attrs ||= person_parser.required_attrs.to_h {|ra| [ra.attr, ra]}
|
153
|
+
end
|
154
|
+
|
155
|
+
def all_internal_attrs
|
156
|
+
@all_internal_attrs ||= [].tap do |int_attrs|
|
157
|
+
known_int_attrs = person_parser.all_attrs(include_defined_parsers: true)
|
158
|
+
known_int_attrs |= fields_mapper.list(:internal).compact
|
159
|
+
int_attrs.concat(known_int_attrs)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Scopes what internal attrs appear in headers as they are
|
164
|
+
def internal_present_or_active(raw_headers, inactive_requirements = {}) # rubocop:disable Metrics/AbcSize
|
165
|
+
# internal attrs that are not being mapped
|
166
|
+
int_all = all_internal_attrs.reject {|i| fields_mapper.external?(i)}
|
167
|
+
hint = raw_headers & int_all
|
168
|
+
hext = raw_headers - hint
|
169
|
+
int_present = hint + hext.map {|e| fields_mapper.to_internal(e)}.compact
|
170
|
+
|
171
|
+
update_inactive = proc do
|
172
|
+
inactive_requirements.dup.each do |attr, req|
|
173
|
+
next unless req.active?(*int_present)
|
174
|
+
|
175
|
+
inactive_requirements.delete(attr)
|
176
|
+
int_present << attr
|
177
|
+
update_inactive.call
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
required_attrs.each_value do |req|
|
182
|
+
next if int_present.include?(req)
|
183
|
+
|
184
|
+
if req.active?(*int_present)
|
185
|
+
inactive_requirements.delete(req.attr)
|
186
|
+
int_present << req.attr
|
187
|
+
update_inactive.call
|
188
|
+
else
|
189
|
+
inactive_requirements[req.attr] = req
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
int_present
|
194
|
+
end
|
195
|
+
|
196
|
+
def person_parser
|
197
|
+
session.entry_factory.person_parser
|
198
|
+
end
|
199
|
+
|
200
|
+
def abort(msg)
|
201
|
+
super(msg, raising: false) if defined?(super)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Eco::API::Common::People
|
2
|
+
class DefaultParsers
|
3
|
+
module Helpers
|
4
|
+
module NullParsing
|
5
|
+
private
|
6
|
+
|
7
|
+
def parse_null(value)
|
8
|
+
return if null?(value)
|
9
|
+
return parse_null_on_hash(value) if value.is_a?(Hash)
|
10
|
+
return value unless value.is_a?(Array)
|
11
|
+
|
12
|
+
value.map {|val| parse_null(val)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_null_on_hash(value)
|
16
|
+
return value unless value.is_a?(Hash)
|
17
|
+
|
18
|
+
value.dup do |out|
|
19
|
+
value.each do |key, val|
|
20
|
+
next out.delete(key) unless (out[key] = parse_null(val))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def null?(value)
|
26
|
+
return true if value.nil?
|
27
|
+
return false unless value.is_a?(String)
|
28
|
+
return true if value.strip.to_s.empty?
|
29
|
+
|
30
|
+
str = value.strip.upcase
|
31
|
+
['NULL'].any? {|token| str == token}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Eco::API::Common::People::DefaultParsers::JsonParser < Eco::API::Common::Loaders::Parser
|
2
|
+
attribute :json
|
3
|
+
|
4
|
+
include Eco::API::Common::People::DefaultParsers::Helpers::ExpectedHeaders
|
5
|
+
include Eco::API::Common::People::DefaultParsers::Helpers::NullParsing
|
6
|
+
|
7
|
+
def parser(filename, deps)
|
8
|
+
parse_json_file(filename).tap do |data|
|
9
|
+
# Don't enable header checks for now
|
10
|
+
next
|
11
|
+
|
12
|
+
puts 'Identifying unified json object keys...'
|
13
|
+
raw_headers = data.each_with_object([]) do |item, head|
|
14
|
+
head.concat(item.keys - head)
|
15
|
+
end
|
16
|
+
|
17
|
+
require_headers!(raw_headers)
|
18
|
+
|
19
|
+
next unless deps[:check_headers]
|
20
|
+
next unless check_headers?
|
21
|
+
|
22
|
+
check_headers!(
|
23
|
+
data,
|
24
|
+
order_check: false
|
25
|
+
)
|
26
|
+
end.each_with_object([]) do |item, arr_hash|
|
27
|
+
item_hash = item.keys.each_with_object({}) do |attr, hash|
|
28
|
+
next if attr.to_s.strip.empty?
|
29
|
+
|
30
|
+
hash[attr.strip] = parse_null(item[attr])
|
31
|
+
end
|
32
|
+
|
33
|
+
arr_hash.push(item_hash)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def serializer(array_hash, _deps)
|
38
|
+
array_hash.to_json
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def check_headers?
|
44
|
+
!options.dig(:input, :header_check, :skip)
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_json_file(filename)
|
48
|
+
fd = File.open(filename)
|
49
|
+
JSON.load fd # rubocop:disable Security/JSONLoad
|
50
|
+
rescue JSON::ParserError => err
|
51
|
+
log(:error) { "Parsing error on file '#{filename}'" }
|
52
|
+
raise err
|
53
|
+
ensure
|
54
|
+
fd&.close
|
55
|
+
end
|
56
|
+
end
|
@@ -2,12 +2,9 @@ class Eco::API::Common::People::DefaultParsers::XLSParser < Eco::API::Common::Lo
|
|
2
2
|
attribute :xls
|
3
3
|
|
4
4
|
attr_accessor :already_required
|
5
|
-
attr_reader :file
|
6
5
|
|
7
|
-
def parser(
|
8
|
-
|
9
|
-
rows.tap do |rws|
|
10
|
-
@file = nil
|
6
|
+
def parser(filename, _deps)
|
7
|
+
rows(file: filename).tap do |rws|
|
11
8
|
rws.each do |row|
|
12
9
|
to_string!(row)
|
13
10
|
end
|
@@ -22,13 +19,14 @@ class Eco::API::Common::People::DefaultParsers::XLSParser < Eco::API::Common::Lo
|
|
22
19
|
|
23
20
|
def to_string!(row)
|
24
21
|
row.transform_values! do |val|
|
25
|
-
next
|
22
|
+
next unless val
|
26
23
|
next val if val.is_a?(String)
|
24
|
+
|
27
25
|
val.to_s
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
31
|
-
def
|
29
|
+
def expected_headers
|
32
30
|
log(:warn) {
|
33
31
|
"Headers detection is using your fields_map.json file (native behaviour)"
|
34
32
|
}
|
@@ -39,30 +37,31 @@ class Eco::API::Common::People::DefaultParsers::XLSParser < Eco::API::Common::Lo
|
|
39
37
|
0
|
40
38
|
end
|
41
39
|
|
42
|
-
def workbook
|
40
|
+
def workbook(file)
|
43
41
|
require_reading_libs!
|
44
42
|
Roo::Spreadsheet.open(file)
|
45
43
|
end
|
46
44
|
|
47
|
-
def spreadheet(name_or_index = sheet_name)
|
48
|
-
workbook.sheet(name_or_index)
|
45
|
+
def spreadheet(name_or_index = sheet_name, file:)
|
46
|
+
workbook(file).sheet(name_or_index)
|
49
47
|
end
|
50
48
|
|
51
|
-
def rows(target =
|
52
|
-
spreadheet.parse(header_search: target, clean: true)
|
49
|
+
def rows(target = expected_headers, file:)
|
50
|
+
spreadheet(file: file).parse(header_search: target, clean: true)
|
53
51
|
rescue Roo::HeaderRowNotFoundError => e
|
54
52
|
missing = JSON.parse(e.message)
|
55
53
|
|
56
54
|
log(:warn) {
|
57
|
-
"The input file is missing these headers: #{missing}"
|
55
|
+
"The input file is missing these expected headers: #{missing}"
|
58
56
|
}
|
59
57
|
|
60
58
|
present = target - missing
|
61
|
-
rows(present)
|
59
|
+
rows(present, file: file)
|
62
60
|
end
|
63
61
|
|
64
62
|
def require_reading_libs!
|
65
63
|
return if already_required
|
64
|
+
|
66
65
|
require 'roo'
|
67
66
|
require 'roo-xls'
|
68
67
|
self.already_required = true
|
@@ -12,6 +12,7 @@ module Eco
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
require_relative 'default_parsers/helpers'
|
15
16
|
require_relative 'default_parsers/select_parser'
|
16
17
|
require_relative 'default_parsers/boolean_parser'
|
17
18
|
require_relative 'default_parsers/numeric_parser'
|
@@ -22,4 +23,5 @@ require_relative 'default_parsers/freemium_parser'
|
|
22
23
|
require_relative 'default_parsers/policy_groups_parser'
|
23
24
|
require_relative 'default_parsers/login_providers_parser'
|
24
25
|
require_relative 'default_parsers/csv_parser'
|
26
|
+
require_relative 'default_parsers/json_parser'
|
25
27
|
require_relative 'default_parsers/xls_parser'
|
@@ -28,6 +28,7 @@ module Eco
|
|
28
28
|
# to translate external names into internal ones and _vice versa_.
|
29
29
|
def initialize(e, schema:, person_parser: nil, default_parser: nil, attr_map: nil)
|
30
30
|
super(e)
|
31
|
+
|
31
32
|
msg = "Constructor needs a PersonSchema. Given: #{schema.class}"
|
32
33
|
fatal msg unless schema.is_a?(Ecoportal::API::V1::PersonSchema)
|
33
34
|
|
@@ -133,9 +134,10 @@ module Eco
|
|
133
134
|
out.concat(curr)
|
134
135
|
end
|
135
136
|
end
|
136
|
-
|
137
|
+
|
138
|
+
# Get content only when it's not :xls, nor :json
|
137
139
|
# note: even if content was provided, file takes precedence
|
138
|
-
if (format
|
140
|
+
if get_content?(format) && file # rubocop:disable Style/IfUnlessModifier
|
139
141
|
content = get_file_content(file, encoding: encoding)
|
140
142
|
end
|
141
143
|
|
@@ -166,8 +168,10 @@ module Eco
|
|
166
168
|
end
|
167
169
|
end.tap do |out_array|
|
168
170
|
start_from_two = (format == :csv) || format == :xls
|
169
|
-
|
170
|
-
|
171
|
+
first_idx = start_from_two ? 2 : 1
|
172
|
+
|
173
|
+
out_array.each.with_index(first_idx) do |entry_hash, idx|
|
174
|
+
entry_hash["idx"] = idx
|
171
175
|
entry_hash["source_file"] = file
|
172
176
|
end
|
173
177
|
end
|
@@ -222,6 +226,13 @@ module Eco
|
|
222
226
|
|
223
227
|
private
|
224
228
|
|
229
|
+
def get_content?(format)
|
230
|
+
return false if format == :xls
|
231
|
+
return false if format == :json
|
232
|
+
|
233
|
+
true
|
234
|
+
end
|
235
|
+
|
225
236
|
def abort(message)
|
226
237
|
log(:error) { message }
|
227
238
|
exit(1)
|
@@ -8,6 +8,7 @@ module Eco
|
|
8
8
|
def initialize(enviro:)
|
9
9
|
invalid_env = enviro && !enviro.is_a?(Eco::API::Common::Session::Environment)
|
10
10
|
raise "Required Environment object (enviro:). Given: #{enviro}" if invalid_env
|
11
|
+
|
11
12
|
@enviro = enviro
|
12
13
|
end
|
13
14
|
|
@@ -103,6 +104,7 @@ module Eco
|
|
103
104
|
def download(files, local_folder: nil, &block)
|
104
105
|
puts "Creating local files:"
|
105
106
|
created_files = []
|
107
|
+
|
106
108
|
[files].flatten.compact.map do |fullname|
|
107
109
|
basename = windows_basename(fullname)
|
108
110
|
dest_fullname = File.join(local_folder || ".", basename)
|
@@ -110,6 +112,7 @@ module Eco
|
|
110
112
|
created_files << dest_fullname
|
111
113
|
sftp_session.download(fullname, dest_fullname)
|
112
114
|
end.each(&:wait)
|
115
|
+
|
113
116
|
# run SSH event loop while dw.active?
|
114
117
|
created_files.tap { created_files.each(&block) }
|
115
118
|
end
|
@@ -155,9 +158,11 @@ module Eco
|
|
155
158
|
def windows_basename(remote_fullname)
|
156
159
|
dir_sep = /[\\\/]/
|
157
160
|
patr_re = /[<>:\\\/|?*]/
|
161
|
+
|
158
162
|
parts = remote_fullname.split(dir_sep).map do |node|
|
159
163
|
node.gsub(patr_re, '_')
|
160
164
|
end
|
165
|
+
|
161
166
|
local_fullname = File.join(*parts)
|
162
167
|
File.basename(local_fullname)
|
163
168
|
end
|
data/lib/eco/api/error.rb
CHANGED
@@ -96,9 +96,11 @@ module Eco
|
|
96
96
|
end.sort do |k_1, k_2|
|
97
97
|
next -1 if k_2 < k_1
|
98
98
|
next 1 if k_1 < k_2
|
99
|
+
|
99
100
|
0
|
100
101
|
end.tap do |siblings|
|
101
102
|
siblings.delete(Unclassified)
|
103
|
+
|
102
104
|
if direct
|
103
105
|
siblings.reject! do |si|
|
104
106
|
siblings.any? {|s| si < s}
|
@@ -120,8 +122,10 @@ module Eco
|
|
120
122
|
|
121
123
|
descendants(direct: true).reverse.each do |klass|
|
122
124
|
next unless klass.err_match?(err_msg)
|
125
|
+
|
123
126
|
type = klass
|
124
127
|
next unless klass.descendants?(direct: true)
|
128
|
+
|
125
129
|
type = klass.get_type(err_msg, first: false) || type
|
126
130
|
end
|
127
131
|
|
@@ -217,6 +217,7 @@ module Eco
|
|
217
217
|
elsif !pqueue.empty?
|
218
218
|
req_backup = as_update(pqueue, add_feedback: false)
|
219
219
|
backup_update(req_backup)
|
220
|
+
|
220
221
|
log(:debug) {
|
221
222
|
"Job ('#{name}':#{type}): going to launch batch against #{pqueue.count} entries"
|
222
223
|
}
|
@@ -246,28 +247,25 @@ module Eco
|
|
246
247
|
# 3. error messages in case they were errors from the server
|
247
248
|
# @note if `launch` was not invoked, it specifies so
|
248
249
|
# @return [String] the summary
|
249
|
-
def summary
|
250
|
+
def summary
|
250
251
|
[].tap do |msg|
|
251
|
-
if pending?
|
252
|
-
|
253
|
-
else
|
254
|
-
msg << feedback.generate(requests, only_stats: true)
|
252
|
+
msg << "PENDING Job -------->\n" if pending?
|
253
|
+
msg << feedback.generate(requests, only_stats: true)
|
255
254
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
end
|
260
|
-
|
261
|
-
msg << status.errors.message if status
|
262
|
-
msg << subjobs_summary
|
255
|
+
if batch_policy && !batch_policy.compliant?(request_stats)
|
256
|
+
msg << 'Batch Policy Uncompliance:'
|
257
|
+
msg << batch_policy.uncompliance(request_stats)
|
263
258
|
end
|
259
|
+
|
260
|
+
msg << status.errors.message if status
|
261
|
+
msg << subjobs_summary
|
264
262
|
end.join("\n")
|
265
263
|
end
|
266
264
|
|
267
265
|
private
|
268
266
|
|
269
267
|
def subjobs_summary
|
270
|
-
return
|
268
|
+
return '' unless subjobs.count.positive?
|
271
269
|
|
272
270
|
[].tap do |msg|
|
273
271
|
subjobs.map {|subjob| msg << subjob.summary}
|
@@ -431,7 +429,7 @@ module Eco
|
|
431
429
|
next unless status.success?(entry)
|
432
430
|
|
433
431
|
if type == :create && entry.respond_to?(:id=)
|
434
|
-
entry.id = status[entry].body[
|
432
|
+
entry.id = status[entry].body['id'].tap do |id|
|
435
433
|
next unless id.to_s.strip.empty?
|
436
434
|
|
437
435
|
ref = Eco::API::Session::Batch::Feedback.person_ref(entry)
|
@@ -503,9 +501,9 @@ module Eco
|
|
503
501
|
|
504
502
|
# Keep a copy of the requests for future reference
|
505
503
|
def backup_update(requests, simulate: false)
|
506
|
-
dry_run = simulate ?
|
504
|
+
dry_run = simulate ? '_dry_run' : ''
|
507
505
|
dir = config.people.requests_folder
|
508
|
-
filename = name.split(
|
506
|
+
filename = name.split(' ').join('-').gsub(/[=\\\/><,"-]+/, "_") # rubocop:disable Style/RedundantArgument
|
509
507
|
file = File.join(dir, "#{type}_data_#{filename}#{dry_run}.json")
|
510
508
|
file_manager.save_json(requests, file, :timestamp)
|
511
509
|
end
|
@@ -81,7 +81,7 @@ module Eco
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def pending?
|
84
|
-
any?
|
84
|
+
any?(&:pending?)
|
85
85
|
end
|
86
86
|
|
87
87
|
# Launches every `Batch::Job` in the group.
|
@@ -108,19 +108,17 @@ module Eco
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
-
def status
|
111
|
+
def status(&block)
|
112
112
|
if block_given?
|
113
|
-
status.each
|
114
|
-
yield(job, job_status)
|
115
|
-
end
|
113
|
+
status.each(&block)
|
116
114
|
self
|
117
|
-
else
|
118
|
-
@jobs_status ||= {}
|
115
|
+
else
|
116
|
+
@jobs_status ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
|
119
117
|
end
|
120
118
|
end
|
121
119
|
|
122
120
|
def errors?
|
123
|
-
any?
|
121
|
+
any?(&:errors?)
|
124
122
|
end
|
125
123
|
|
126
124
|
def summary
|
@@ -18,14 +18,17 @@ module Eco
|
|
18
18
|
private
|
19
19
|
|
20
20
|
# Swaps to batch endpoint on specific errors
|
21
|
-
def batch_mode_on(*error_types, options: self.options, allow_job_mode: true
|
21
|
+
def batch_mode_on(*error_types, options: self.options, allow_job_mode: true)
|
22
|
+
msg = "Expecting block. Non given"
|
23
|
+
raise ArgumentError, msg unless block_given?
|
24
|
+
|
22
25
|
in_job_mode = allow_job_mode && job_mode?(options)
|
23
26
|
|
24
27
|
yield(in_job_mode, batch_size(options))
|
25
28
|
rescue *error_types
|
26
29
|
raise unless in_job_mode
|
27
30
|
|
28
|
-
yield(false
|
31
|
+
yield(false, batch_mode_size)
|
29
32
|
end
|
30
33
|
|
31
34
|
# MODE
|
@@ -25,7 +25,12 @@ module Eco
|
|
25
25
|
explanation << "You have #{retries_left} retries left."
|
26
26
|
question = " Do you want to retry (y/N)?"
|
27
27
|
|
28
|
-
prompt_user(
|
28
|
+
prompt_user(
|
29
|
+
question,
|
30
|
+
default: "Y",
|
31
|
+
explanation: explanation,
|
32
|
+
timeout: 10
|
33
|
+
) do |response|
|
29
34
|
raise unless response.upcase.start_with?("Y")
|
30
35
|
|
31
36
|
puts "\nOkay... let's retry!"
|
@@ -6,14 +6,16 @@ module Eco
|
|
6
6
|
module StatusHandling
|
7
7
|
private
|
8
8
|
|
9
|
-
def tap_status(enviro:, queue:, method:, status: nil
|
9
|
+
def tap_status(enviro:, queue:, method:, status: nil)
|
10
10
|
status ||= Eco::API::Session::Batch::Status.new(
|
11
11
|
enviro,
|
12
12
|
queue: queue,
|
13
13
|
method: method
|
14
14
|
)
|
15
15
|
|
16
|
-
status.tap
|
16
|
+
status.tap do
|
17
|
+
yield(status) if block_given?
|
18
|
+
end
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|