eco-helpers 2.0.11 → 2.0.16

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