eco-helpers 2.0.11 → 2.0.16
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 +44 -71
- data/eco-helpers.gemspec +1 -1
- data/lib/eco/api/common/loaders/use_case.rb +1 -1
- data/lib/eco/api/common/people/person_entry.rb +5 -0
- data/lib/eco/api/common/people/supervisor_helpers.rb +27 -0
- data/lib/eco/api/common/session/file_manager.rb +2 -2
- data/lib/eco/api/error.rb +5 -3
- data/lib/eco/api/organization/presets_factory.rb +166 -82
- data/lib/eco/api/organization/presets_integrity.json +52 -0
- data/lib/eco/api/session/batch.rb +19 -3
- data/lib/eco/api/session/batch/job.rb +3 -0
- data/lib/eco/api/session/config.rb +16 -5
- data/lib/eco/api/session/config/api.rb +4 -0
- data/lib/eco/api/session/config/apis.rb +14 -0
- data/lib/eco/api/session/config/files.rb +7 -0
- data/lib/eco/api/session/config/people.rb +2 -2
- data/lib/eco/api/usecases.rb +2 -0
- data/lib/eco/api/usecases/default_cases.rb +3 -0
- data/lib/eco/api/usecases/default_cases/abstract_policygroup_abilities_case.rb +161 -0
- data/lib/eco/api/usecases/default_cases/hris_case.rb +14 -8
- data/lib/eco/api/usecases/default_cases/supers_cyclic_identify_case.rb +72 -0
- data/lib/eco/api/usecases/default_cases/supers_hierarchy_case.rb +59 -0
- data/lib/eco/api/usecases/default_cases/to_csv_case.rb +41 -21
- data/lib/eco/cli.rb +1 -10
- data/lib/eco/cli/config/default/usecases.rb +20 -2
- data/lib/eco/cli/config/default/workflow.rb +5 -0
- data/lib/eco/data/files/directory.rb +28 -20
- data/lib/eco/version.rb +1 -1
- metadata +5 -1
@@ -3,39 +3,49 @@ module Eco
|
|
3
3
|
module Organization
|
4
4
|
|
5
5
|
class PresetsFactory
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def all_abilities(hash = {})
|
10
|
+
Hash[abilities.each_with_object(nil).to_a].merge(hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
def abilities_model
|
14
|
+
@abilities_model ||= JSON.load(File.open(ABILITIES))
|
15
|
+
end
|
16
|
+
|
17
|
+
def integrity_model
|
18
|
+
@integrity_model ||= JSON.load(File.open(INTEGRITY))
|
19
|
+
end
|
20
|
+
|
21
|
+
def abilities
|
22
|
+
@abilities ||= abilities_model.keys
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
6
27
|
ABILITIES = File.join(__dir__, 'presets_values.json')
|
28
|
+
INTEGRITY = File.join(__dir__, 'presets_integrity.json')
|
7
29
|
DEFAULT_CUSTOM = 'presets_custom.json'
|
8
30
|
DEFAULT_MAP = 'presets_map.json'
|
9
31
|
|
10
32
|
def initialize(presets_custom: DEFAULT_CUSTOM, presets_map: DEFAULT_MAP, enviro: nil, policy_groups: nil)
|
11
|
-
@abilities = JSON.load(File.open(ABILITIES))
|
12
|
-
@habilities = @abilities.map do |key, values|
|
13
|
-
h_values = values.map { |v| [v, true] }.to_h
|
14
|
-
[key, h_values]
|
15
|
-
end.to_h
|
16
|
-
|
17
33
|
fatal("Expecting Environment object. Given: #{enviro}") if enviro && !enviro.is_a?(Eco::API::Common::Session::Environment)
|
18
|
-
@enviro
|
19
|
-
|
20
|
-
policy_groups = policy_groups || @enviro&.api&.policy_groups.to_a
|
21
|
-
if policy_groups.is_a?(Eco::API::Organization::PolicyGroups)
|
22
|
-
@policy_groups = policy_groups
|
23
|
-
else
|
24
|
-
@policy_groups = Eco::API::Organization::PolicyGroups.new(policy_groups)
|
25
|
-
end
|
34
|
+
@enviro = enviro
|
26
35
|
|
27
|
-
|
28
|
-
|
36
|
+
@policy_groups = policy_groups
|
37
|
+
@presets_custom_file = presets_custom || DEFAULT_CUSTOM
|
38
|
+
@presets_map_file = presets_map || DEFAULT_MAP
|
29
39
|
end
|
30
40
|
|
31
41
|
def new(*policy_group_ids_or_names)
|
32
42
|
|
33
43
|
names = policy_group_ids_or_names.map do |id_name|
|
34
|
-
|
44
|
+
policy_groups.to_name(id_name)&.downcase
|
35
45
|
end.compact
|
36
46
|
|
37
|
-
if
|
38
|
-
preset_names = names.map { |name|
|
47
|
+
if presets_map
|
48
|
+
preset_names = names.map { |name| presets_map.fetch(name, nil) }
|
39
49
|
else # option to do not use preset mapping (so just the policy group name)
|
40
50
|
preset_names = names
|
41
51
|
end
|
@@ -44,69 +54,11 @@ module Eco
|
|
44
54
|
|
45
55
|
# @return [Array<String>] all the abilities
|
46
56
|
def keys
|
47
|
-
|
57
|
+
abilities_model.keys
|
48
58
|
end
|
49
59
|
|
50
60
|
private
|
51
61
|
|
52
|
-
def init_custom(file = DEFAULT_CUSTOM)
|
53
|
-
@presets_custom = nil
|
54
|
-
|
55
|
-
return if !file
|
56
|
-
file = File.expand_path(file)
|
57
|
-
|
58
|
-
if File.exists?(file)
|
59
|
-
@presets_custom = JSON.load(File.open(file))
|
60
|
-
|
61
|
-
errors = @presets_custom.map do |key, preset|
|
62
|
-
(err = preset_errors(preset)) ? "{ '#{key}' preset -> #{err}}": nil
|
63
|
-
end.compact
|
64
|
-
|
65
|
-
fatal("File '#{file}' contains invalid presets:\n #{errors.join("\n ")}") if errors.length > 0
|
66
|
-
end
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
def init_map(file = DEFAULT_MAP)
|
71
|
-
@presets_map = nil
|
72
|
-
|
73
|
-
return if !file
|
74
|
-
file = File.expand_path(file)
|
75
|
-
|
76
|
-
if File.exists?(file)
|
77
|
-
fatal("Maps file specified without custom presets file. Aborting!") if !@presets_custom
|
78
|
-
@presets_map = JSON.load(File.open(file))
|
79
|
-
|
80
|
-
errors = []
|
81
|
-
if @policy_groups.length > 0
|
82
|
-
errors = @policy_groups.map do |pg|
|
83
|
-
exists = @presets_map[pg.name.downcase] || @presets_custom[pg.name.downcase]
|
84
|
-
exists ? nil : "'#{pg.name}'"
|
85
|
-
end.compact
|
86
|
-
|
87
|
-
warn("No maps or no preset for policy group(s): #{errors.join(", ")}") if errors.length > 0
|
88
|
-
end
|
89
|
-
|
90
|
-
errors = @presets_map.map do |source, dest|
|
91
|
-
@presets_custom[dest] ? nil : "'#{dest}'"
|
92
|
-
end.compact
|
93
|
-
|
94
|
-
warn("Unexisting mapped preset(s): #{errors.uniq.join(", ")}") if errors.length > 0
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
def fatal(msg)
|
100
|
-
raise msg if !@enviro
|
101
|
-
@enviro.logger.fatal(msg)
|
102
|
-
raise msg
|
103
|
-
end
|
104
|
-
|
105
|
-
def warn(msg)
|
106
|
-
raise msg if !@enviro
|
107
|
-
@enviro.logger.warn(msg)
|
108
|
-
end
|
109
|
-
|
110
62
|
def compile(*preset_names)
|
111
63
|
fatal("You need to specify an existing file for the custom presets.") if !@presets_custom
|
112
64
|
@presets_custom.values_at(*preset_names).compact.reduce({}) do |p1, p2|
|
@@ -117,7 +69,7 @@ module Eco
|
|
117
69
|
def merge(preset1, preset2)
|
118
70
|
keys = preset1.keys | preset2.keys
|
119
71
|
|
120
|
-
|
72
|
+
abilities_model.each_with_object({}) do |(key, values), result|
|
121
73
|
next unless keys.include?(key)
|
122
74
|
idx = [
|
123
75
|
values.index(preset1[key]),
|
@@ -129,17 +81,149 @@ module Eco
|
|
129
81
|
|
130
82
|
# unsused: only play with the given abilities
|
131
83
|
def empty_model
|
132
|
-
JSON.parse(
|
84
|
+
JSON.parse(abilities_model.to_json).transform_values {|v| nil }
|
133
85
|
end
|
134
86
|
|
135
87
|
def preset_errors(preset)
|
136
88
|
return "No preset given" if !preset
|
137
89
|
errors = preset.map do |k, v|
|
138
|
-
|
90
|
+
value_exists?(k, v) ? nil : "#{k}:#{v}"
|
139
91
|
end.compact
|
140
|
-
return "
|
92
|
+
return " Unknown: {#{errors.join(", ")}}" if errors.length > 0
|
141
93
|
nil
|
142
94
|
end
|
95
|
+
|
96
|
+
def preset_integrity(preset)
|
97
|
+
preset.each_with_object([]) do |(ability, value), errors|
|
98
|
+
next unless checks = integrity_model[ability]
|
99
|
+
|
100
|
+
suberrors = []
|
101
|
+
|
102
|
+
checks.each do |check|
|
103
|
+
next unless check["value"] == value
|
104
|
+
check["conditions"].each do |cond, targets|
|
105
|
+
case cond
|
106
|
+
when "at_least"
|
107
|
+
targets.each do |other, minimum|
|
108
|
+
unless (ability_value_idx(other, minimum) <= ability_value_idx(other, preset[other]))
|
109
|
+
suberrors << "'#{other}' should be at least '#{minimum}'"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
when "one_of"
|
113
|
+
unless targets.any? {|other, expected| preset[other] == expected}
|
114
|
+
suberrors << targets.each_with_object([]) do |(other, expected), out|
|
115
|
+
out << "'#{other}': '#{expected}'"
|
116
|
+
end.join(", ").yield_self do |msg|
|
117
|
+
"there should be at least one of: {#{msg}}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
else
|
121
|
+
warn("Unsuported integrity condition statement '#{cond}' in '#{ability}' with level '#{value}'")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
if suberrors.length > 0
|
127
|
+
errors << "Incorrect value '#{value}' for '#{ability}' - reasons: {#{suberrors.join(", ")}}"
|
128
|
+
end
|
129
|
+
end.yield_self do |errors|
|
130
|
+
" Integrity errors: { #{errors.join(", ")} }" if errors.length > 0
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def integrity_model
|
135
|
+
self.class.integrity_model
|
136
|
+
end
|
137
|
+
|
138
|
+
def value_exists?(ability, value)
|
139
|
+
abilities_model_inverted.dig(ability, value)
|
140
|
+
end
|
141
|
+
|
142
|
+
def abilities_model_inverted
|
143
|
+
@abilities_model_inverted ||= abilities_model.each_with_object({}) do |(key, values), out|
|
144
|
+
out[key] = values.each_with_object({}) {|v, h| h[v] = true }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def ability_value_idx(ability, value)
|
149
|
+
abilities_model[ability].index(value)
|
150
|
+
end
|
151
|
+
|
152
|
+
def abilities_model
|
153
|
+
self.class.abilities_model
|
154
|
+
end
|
155
|
+
|
156
|
+
def policy_groups
|
157
|
+
return @policy_groups if @policy_groups.is_a?(Eco::API::Organization::PolicyGroups)
|
158
|
+
@policy_groups ||= @enviro&.api&.policy_groups.to_a
|
159
|
+
|
160
|
+
unless @policy_groups.is_a?(Eco::API::Organization::PolicyGroups)
|
161
|
+
@policy_groups = Eco::API::Organization::PolicyGroups.new(@policy_groups)
|
162
|
+
end
|
163
|
+
@policy_groups
|
164
|
+
end
|
165
|
+
|
166
|
+
def presets_custom
|
167
|
+
return @presets_custom if instance_variable_defined?(:@presets_custom)
|
168
|
+
@presets_custom = nil
|
169
|
+
if @presets_custom_file
|
170
|
+
if (file = File.expand_path(@presets_custom_file)) && File.exists?(file)
|
171
|
+
@presets_custom = JSON.load(File.open(file)).tap do |custom_presets|
|
172
|
+
errors = custom_presets.each_with_object([]) do |(key, preset), errors|
|
173
|
+
if err = preset_errors(preset)
|
174
|
+
errors << "{ '#{key}' preset -> #{err}}"
|
175
|
+
end
|
176
|
+
if err = preset_integrity(preset)
|
177
|
+
errors << "{ '#{key}' preset -> #{err}}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
fatal("File '#{file}' contains invalid presets:\n #{errors.join("\n ")}") if errors.length > 0
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def presets_map
|
188
|
+
return @presets_map if instance_variable_defined?(:@presets_map)
|
189
|
+
@presets_map = nil
|
190
|
+
if @presets_map_file
|
191
|
+
if (file = File.expand_path(@presets_map_file)) && File.exists?(file)
|
192
|
+
fatal("Maps file specified without 'presets_custom.json' file. Aborting!") if !presets_custom
|
193
|
+
@presets_map = JSON.load(File.open(file)).tap do |map_presets|
|
194
|
+
|
195
|
+
errors = []
|
196
|
+
if policy_groups.length > 0
|
197
|
+
errors = policy_groups.map do |pg|
|
198
|
+
exists = map_presets[pg.name.downcase] || presets_custom[pg.name.downcase]
|
199
|
+
exists ? nil : "'#{pg.name}'"
|
200
|
+
end.compact
|
201
|
+
|
202
|
+
warn("No maps or no preset for policy group(s): #{errors.join(", ")}") if errors.length > 0
|
203
|
+
end
|
204
|
+
|
205
|
+
errors = map_presets.map do |source, dest|
|
206
|
+
presets_custom[dest] ? nil : "'#{dest}'"
|
207
|
+
end.compact
|
208
|
+
|
209
|
+
warn("Unexisting mapped preset(s): #{errors.uniq.join(", ")}") if errors.length > 0
|
210
|
+
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def fatal(msg)
|
217
|
+
raise msg if !@enviro
|
218
|
+
@enviro.logger.fatal(msg)
|
219
|
+
raise msg
|
220
|
+
end
|
221
|
+
|
222
|
+
def warn(msg)
|
223
|
+
raise msg if !@enviro
|
224
|
+
@enviro.logger.warn(msg)
|
225
|
+
end
|
226
|
+
|
143
227
|
end
|
144
228
|
|
145
229
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
{
|
2
|
+
"person_core_create": [
|
3
|
+
{ "value": "create", "conditions": {
|
4
|
+
"at_least": {"person_core": "view_people_manager"}
|
5
|
+
}
|
6
|
+
}
|
7
|
+
],
|
8
|
+
"person_core_edit": [
|
9
|
+
{ "value": "edit", "conditions": {
|
10
|
+
"at_least": {"person_core": "view_people_manager"}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
],
|
14
|
+
"person_details": [
|
15
|
+
{ "value": "view", "conditions": {
|
16
|
+
"at_least": {"person_core": "attach"}
|
17
|
+
}
|
18
|
+
},
|
19
|
+
{ "value": "edit_public", "conditions": {
|
20
|
+
"one_of": {
|
21
|
+
"person_core_edit": "edit",
|
22
|
+
"person_core_create": "create"
|
23
|
+
}
|
24
|
+
}
|
25
|
+
},
|
26
|
+
{ "value": "view_private", "conditions": {
|
27
|
+
"at_least": {"person_core": "attach" }
|
28
|
+
}
|
29
|
+
},
|
30
|
+
{ "value": "edit_private", "conditions": {
|
31
|
+
"one_of": {
|
32
|
+
"person_core_edit": "edit",
|
33
|
+
"person_core_create": "create"
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
],
|
38
|
+
"person_account": [
|
39
|
+
{ "value": "view", "conditions": {
|
40
|
+
"at_least": {"person_core": "attach" }
|
41
|
+
}
|
42
|
+
},
|
43
|
+
{ "value": "create", "conditions": {
|
44
|
+
"at_least": {"person_core_create": "create"}
|
45
|
+
}
|
46
|
+
},
|
47
|
+
{ "value": "edit", "conditions": {
|
48
|
+
"at_least": {"person_core_edit": "edit"}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
]
|
52
|
+
}
|
@@ -105,10 +105,15 @@ module Eco
|
|
105
105
|
iterations = (data.length.to_f / per_page).ceil
|
106
106
|
|
107
107
|
Eco::API::Session::Batch::Status.new(enviro, queue: data, method: method).tap do |status|
|
108
|
+
start_time = Time.now
|
109
|
+
start_slice = Time.now; slice = []
|
108
110
|
data.each_slice(per_page) do |slice|
|
109
|
-
msg
|
111
|
+
msg = "starting batch '#{method}' iteration #{iteration}/#{iterations},"
|
112
|
+
msg += " with #{slice.length} entries of #{data.length} -- #{done} done"
|
113
|
+
msg += " (last: #{str_stats(start_slice, slice.length)}; total: #{str_stats(start_time, done)})"
|
110
114
|
logger.info(msg) unless silent
|
111
115
|
|
116
|
+
start_slice = Time.now
|
112
117
|
people_api.batch do |batch|
|
113
118
|
slice.each do |person|
|
114
119
|
batch.public_send(method, person) do |response|
|
@@ -118,12 +123,23 @@ module Eco
|
|
118
123
|
end
|
119
124
|
end # next batch
|
120
125
|
|
121
|
-
iteration
|
122
|
-
done
|
126
|
+
iteration += 1
|
127
|
+
done += slice.length
|
123
128
|
end # next slice
|
124
129
|
end
|
125
130
|
end
|
126
131
|
|
132
|
+
def str_stats(start, count)
|
133
|
+
now = Time.now
|
134
|
+
secs = (now - start).round(3)
|
135
|
+
if secs > 0.0
|
136
|
+
per_sec = (count.to_f / secs).round(2)
|
137
|
+
"#{secs}s -> #{per_sec} people/s"
|
138
|
+
else
|
139
|
+
" -- "
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
127
143
|
end
|
128
144
|
end
|
129
145
|
end
|
@@ -288,10 +288,13 @@ module Eco
|
|
288
288
|
handlers = session.config.error_handlers
|
289
289
|
if status.errors.any? && !handlers.empty? && !error_handler?
|
290
290
|
err_types = status.errors.by_type
|
291
|
+
logger.debug("(#{self.name}) got these error types: #{err_types.keys}")
|
291
292
|
handlers.each do |handler|
|
292
293
|
if entries = err_types[handler.name]
|
293
294
|
handler_job = subjobs_add("#{self.name} => #{handler.name}", usecase: handler)
|
295
|
+
logger.debug("Running error handler #{handler.name}")
|
294
296
|
handler.launch(people: people(entries), session: session, options: options, job: handler_job)
|
297
|
+
logger.debug("Launching job of error handler: #{handler_job.name}")
|
295
298
|
handler_job.launch(simulate: simulate)
|
296
299
|
end
|
297
300
|
end
|
@@ -159,6 +159,8 @@ module Eco
|
|
159
159
|
end
|
160
160
|
|
161
161
|
def working_directory(mode: nil)
|
162
|
+
return files.working_directory if apis.active_api&.one_off?
|
163
|
+
|
162
164
|
unless mode
|
163
165
|
wd = files.working_directory
|
164
166
|
return wd unless wd.to_s.strip.empty?
|
@@ -183,12 +185,21 @@ module Eco
|
|
183
185
|
end
|
184
186
|
|
185
187
|
def require(file = nil, match: nil)
|
186
|
-
|
187
|
-
|
188
|
-
|
188
|
+
begin
|
189
|
+
if match
|
190
|
+
file_manager.dir.dir_files(pattern: match).each do |file|
|
191
|
+
require_relative File.expand_path(file)
|
192
|
+
end
|
193
|
+
else
|
194
|
+
target = File.expand_path(file_manager.dir.file(file))
|
195
|
+
require_relative target
|
196
|
+
end
|
197
|
+
rescue LoadError => e
|
198
|
+
if apis.active_api.one_off?
|
199
|
+
pp e.to_s
|
200
|
+
else
|
201
|
+
raise
|
189
202
|
end
|
190
|
-
else
|
191
|
-
require_relative "#{File.expand_path(file_manager.dir.file(file))}"
|
192
203
|
end
|
193
204
|
end
|
194
205
|
# @!endgroup
|