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.
@@ -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 = 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
- init_custom(presets_custom)
28
- init_map(presets_map)
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
- @policy_groups.to_name(id_name)&.downcase
44
+ policy_groups.to_name(id_name)&.downcase
35
45
  end.compact
36
46
 
37
- if @presets_map
38
- preset_names = names.map { |name| @presets_map.fetch(name, nil) }
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
- @abilities.keys
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
- @abilities.each_with_object({}) do |(key, values), result|
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(@abilities.to_json).transform_values {|v| nil }
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
- @habilities.dig(k, v) ? nil : "#{k}:#{v}"
90
+ value_exists?(k, v) ? nil : "#{k}:#{v}"
139
91
  end.compact
140
- return " unknown: {#{errors.join(", ")}}" if errors.length > 0
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 = "starting batch '#{method}' iteration #{iteration}/#{iterations}, with #{slice.length} entries of #{data.length} -- #{done} done"
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 += 1
122
- done += slice.length
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
- if match
187
- file_manager.dir.dir_files(pattern: match).each do |file|
188
- require_relative File.expand_path(file)
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
@@ -83,6 +83,10 @@ module Eco
83
83
  self["name"]
84
84
  end
85
85
 
86
+ def one_off?
87
+ name.is_a?(Symbol)
88
+ end
89
+
86
90
  def key
87
91
  self["key"]
88
92
  end