eco-helpers 2.0.14 → 2.0.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -2
  3. data/eco-helpers.gemspec +6 -4
  4. data/lib/eco-helpers.rb +2 -0
  5. data/lib/eco/api/common/base_loader.rb +14 -0
  6. data/lib/eco/api/common/people/default_parsers/date_parser.rb +11 -1
  7. data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +1 -1
  8. data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +11 -11
  9. data/lib/eco/api/common/people/entry_factory.rb +26 -9
  10. data/lib/eco/api/common/people/person_entry.rb +5 -2
  11. data/lib/eco/api/common/people/supervisor_helpers.rb +27 -0
  12. data/lib/eco/api/common/session.rb +1 -0
  13. data/lib/eco/api/common/session/base_session.rb +2 -0
  14. data/lib/eco/api/common/session/file_manager.rb +2 -2
  15. data/lib/eco/api/common/session/helpers.rb +30 -0
  16. data/lib/eco/api/common/session/helpers/prompt_user.rb +34 -0
  17. data/lib/eco/api/common/session/mailer.rb +0 -1
  18. data/lib/eco/api/common/session/s3_uploader.rb +0 -1
  19. data/lib/eco/api/common/session/sftp.rb +0 -1
  20. data/lib/eco/api/common/version_patches/ecoportal_api/external_person.rb +1 -1
  21. data/lib/eco/api/common/version_patches/ecoportal_api/internal_person.rb +7 -4
  22. data/lib/eco/api/common/version_patches/exception.rb +8 -4
  23. data/lib/eco/api/microcases.rb +3 -1
  24. data/lib/eco/api/microcases/append_usergroups.rb +0 -1
  25. data/lib/eco/api/microcases/people_cache.rb +2 -2
  26. data/lib/eco/api/microcases/people_load.rb +2 -2
  27. data/lib/eco/api/microcases/people_refresh.rb +2 -2
  28. data/lib/eco/api/microcases/people_search.rb +6 -6
  29. data/lib/eco/api/microcases/preserve_default_tag.rb +23 -0
  30. data/lib/eco/api/microcases/preserve_filter_tags.rb +28 -0
  31. data/lib/eco/api/microcases/preserve_policy_groups.rb +30 -0
  32. data/lib/eco/api/microcases/set_account.rb +0 -1
  33. data/lib/eco/api/microcases/with_each.rb +67 -6
  34. data/lib/eco/api/microcases/with_each_present.rb +4 -2
  35. data/lib/eco/api/microcases/with_each_starter.rb +4 -2
  36. data/lib/eco/api/organization.rb +1 -0
  37. data/lib/eco/api/organization/people.rb +98 -22
  38. data/lib/eco/api/organization/people_similarity.rb +112 -0
  39. data/lib/eco/api/organization/person_schemas.rb +5 -1
  40. data/lib/eco/api/organization/policy_groups.rb +5 -1
  41. data/lib/eco/api/organization/presets_factory.rb +40 -80
  42. data/lib/eco/api/organization/presets_integrity.json +6 -0
  43. data/lib/eco/api/organization/presets_values.json +5 -4
  44. data/lib/eco/api/policies/default_policies/99_user_access_policy.rb +0 -30
  45. data/lib/eco/api/session.rb +6 -22
  46. data/lib/eco/api/session/batch.rb +25 -7
  47. data/lib/eco/api/session/config.rb +16 -15
  48. data/lib/eco/api/session/config/api.rb +4 -0
  49. data/lib/eco/api/session/config/apis.rb +80 -0
  50. data/lib/eco/api/session/config/files.rb +7 -0
  51. data/lib/eco/api/session/config/people.rb +3 -19
  52. data/lib/eco/api/usecases/default_cases.rb +4 -1
  53. data/lib/eco/api/usecases/default_cases/abstract_policygroup_abilities_case.rb +161 -0
  54. data/lib/eco/api/usecases/default_cases/analyse_people_case.rb +53 -0
  55. data/lib/eco/api/usecases/default_cases/codes_to_tags_case.rb +2 -3
  56. data/lib/eco/api/usecases/default_cases/reset_landing_page_case.rb +11 -1
  57. data/lib/eco/api/usecases/default_cases/restore_db_case.rb +1 -2
  58. data/lib/eco/api/usecases/default_cases/supers_cyclic_identify_case.rb +72 -0
  59. data/lib/eco/api/usecases/default_cases/supers_hierarchy_case.rb +59 -0
  60. data/lib/eco/api/usecases/default_cases/to_csv_case.rb +132 -29
  61. data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +61 -36
  62. data/lib/eco/api/usecases/ooze_samples/ooze_update_case.rb +3 -2
  63. data/lib/eco/cli.rb +0 -10
  64. data/lib/eco/cli/config/default/options.rb +20 -17
  65. data/lib/eco/cli/config/default/people_filters.rb +3 -3
  66. data/lib/eco/cli/config/default/usecases.rb +80 -26
  67. data/lib/eco/cli/config/default/workflow.rb +16 -4
  68. data/lib/eco/cli/config/help.rb +1 -0
  69. data/lib/eco/cli/config/options_set.rb +106 -13
  70. data/lib/eco/cli/config/use_cases.rb +33 -33
  71. data/lib/eco/cli/scripting/args_helpers.rb +30 -3
  72. data/lib/eco/csv.rb +4 -2
  73. data/lib/eco/data.rb +1 -0
  74. data/lib/eco/data/crypto/encryption.rb +3 -3
  75. data/lib/eco/data/files/directory.rb +28 -20
  76. data/lib/eco/data/files/helpers.rb +6 -4
  77. data/lib/eco/data/fuzzy_match.rb +161 -0
  78. data/lib/eco/data/fuzzy_match/array_helpers.rb +75 -0
  79. data/lib/eco/data/fuzzy_match/chars_position_score.rb +37 -0
  80. data/lib/eco/data/fuzzy_match/ngrams_score.rb +78 -0
  81. data/lib/eco/data/fuzzy_match/pairing.rb +101 -0
  82. data/lib/eco/data/fuzzy_match/result.rb +73 -0
  83. data/lib/eco/data/fuzzy_match/results.rb +59 -0
  84. data/lib/eco/data/fuzzy_match/score.rb +44 -0
  85. data/lib/eco/data/fuzzy_match/stop_words.rb +35 -0
  86. data/lib/eco/data/fuzzy_match/string_helpers.rb +69 -0
  87. data/lib/eco/version.rb +1 -1
  88. metadata +87 -10
  89. data/lib/eco/api/microcases/refresh_abilities.rb +0 -19
  90. data/lib/eco/api/organization/presets_reference.json +0 -59
  91. data/lib/eco/api/usecases/default_cases/refresh_abilities_case.rb +0 -30
@@ -114,14 +114,16 @@ module Eco
114
114
  logger.info(msg) unless silent
115
115
 
116
116
  start_slice = Time.now
117
- people_api.batch do |batch|
118
- slice.each do |person|
119
- batch.public_send(method, person) do |response|
120
- faltal("Request with no response") unless !!response
121
- status[person] = response
117
+ offer_retry_on(Ecoportal::API::Errors::TimeOut) do
118
+ people_api.batch do |batch|
119
+ slice.each do |person|
120
+ batch.public_send(method, person) do |response|
121
+ faltal("Request with no response") unless !!response
122
+ status[person] = response
123
+ end
122
124
  end
123
- end
124
- end # next batch
125
+ end # end batch
126
+ end
125
127
 
126
128
  iteration += 1
127
129
  done += slice.length
@@ -129,6 +131,22 @@ module Eco
129
131
  end
130
132
  end
131
133
 
134
+ def offer_retry_on(error_type, retries_left = 3, &block)
135
+ begin
136
+ block.call
137
+ rescue error_type => e
138
+ raise unless retries_left > 0
139
+ explanation = "Batch TimeOut. You have #{retries_left} retries left."
140
+ prompt_user("Do you want to retry (y/N)?", explanation, default: "Y", timeout: 10) do |response|
141
+ if response.upcase.start_with?("Y")
142
+ offer_retry_on(error_type, retries_left - 1, &block)
143
+ else
144
+ raise
145
+ end
146
+ end
147
+ end
148
+ end
149
+
132
150
  def str_stats(start, count)
133
151
  now = Time.now
134
152
  secs = (now - start).round(3)
@@ -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
@@ -286,16 +297,6 @@ module Eco
286
297
  people.default_schema = name
287
298
  end
288
299
 
289
- # Specify the file with the account custom abilities presets
290
- def presets_custom=(file)
291
- people.presets_custom = file
292
- end
293
-
294
- # Specify the file with the usergroup to custom presets mapping
295
- def presets_map=(file)
296
- people.presets_map = file
297
- end
298
-
299
300
  # @see Eco::API::Session::Config::People
300
301
  # @param (see Eco::API::Session::Config::People)
301
302
  # @return [Eco::API::Common::People::PersonParser] parser/serializer for the defined `format`.
@@ -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
@@ -82,6 +82,86 @@ module Eco
82
82
  self["user_key"]
83
83
  end
84
84
 
85
+ # Method to support CLI one-off API requests
86
+ def one_off
87
+ if one_off?
88
+ add(one_off_org, key: one_off_key, host: "#{one_off_enviro}.ecoportal.com")
89
+ return one_off_org
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def one_off?
96
+ @is_one_off ||= SCR.get_arg("-api-key") || SCR.get_arg("-one-off")
97
+ end
98
+
99
+ def one_off_key
100
+ return @one_off_key if instance_variable_defined?(:@one_off_key)
101
+ if one_off?
102
+ Dotenv.load("./.env_one_off")
103
+ SCR.get_arg("-api-key", with_param: true).yield_self do |key|
104
+ one_off_key_env(key)
105
+ end
106
+ end
107
+ end
108
+
109
+ def one_off_key_env(key)
110
+ if one_off?
111
+ if key
112
+ env_file_set_var("./.env_one_off", one_off_key_env_var, key)
113
+ key
114
+ else
115
+ Dotenv.load("./.env_one_off")
116
+ ENV[one_off_key_env_var].tap do |k|
117
+ raise "At least the first time, you should provide the -api-key" unless k
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def one_off_key_env_var
124
+ @one_off_key_env_var ||= "#{one_off_org}_KEY"
125
+ end
126
+
127
+ def one_off_org
128
+ return @one_off_org if instance_variable_defined?(:@one_off_org)
129
+ unless org = SCR.get_arg("-org", with_param: true)
130
+ raise("You should specify -org NAME when using -api-key or -one-off")
131
+ end
132
+ @one_off_org ||= "#{org.downcase.split(/[^a-z]+/).join("_")}_#{one_off_enviro.gsub(".", "_")}".to_sym
133
+ end
134
+
135
+ def one_off_enviro
136
+ return @one_off_enviro if instance_variable_defined?(:@one_off_enviro)
137
+ enviro = SCR.get_arg("-enviro") ? SCR.get_arg("-enviro", with_param: true) : "live"
138
+ @one_off_enviro ||= enviro.downcase
139
+ end
140
+
141
+ def env_file_set_var(file, var, value)
142
+ begin
143
+ pattern = /"#{var}=(?<value>[^ \r\n]+)"/
144
+ File.open(file, "w+") do |fd|
145
+ found = false
146
+ fd.each_line do |line|
147
+ if match = line.match(pattern)
148
+ found = true
149
+ # IO::SEEK_CUR => Seeks to _amount_ plus current position
150
+ fd.seek(-(line.length + 1), IO::SEEK_CUR)
151
+ fd.write line.gsub(match[:value], value)
152
+ end
153
+ end
154
+
155
+ fd << "#{var}=#{value}" unless found
156
+ end
157
+ rescue StandardError => e
158
+ puts "#{e}"
159
+ return false
160
+ end
161
+ return true
162
+ end
163
+
164
+
85
165
  end
86
166
  end
87
167
  end
@@ -14,6 +14,13 @@ module Eco
14
14
  end
15
15
 
16
16
  def working_directory
17
+ if config.apis.active_api&.one_off?
18
+ one_off_dir = File.join("one_off", config.apis.active_name.to_s)
19
+ unless full_path = Eco::Data::Files::Directory.new(one_off_dir).create
20
+ raise "Could not create the folder '#{full_path}'"
21
+ end
22
+ self["dir"] = one_off_dir
23
+ end
17
24
  self["dir"]
18
25
  end
19
26
 
@@ -9,7 +9,7 @@ module Eco
9
9
  end
10
10
 
11
11
  def cache
12
- self["cache"]
12
+ self["cache"] ||= "cache/people.json"
13
13
  end
14
14
 
15
15
  def partial_cache
@@ -35,7 +35,7 @@ module Eco
35
35
  end
36
36
 
37
37
  def requests_folder
38
- self["requests_folder"]
38
+ self["requests_folder"] ||= "requests"
39
39
  end
40
40
 
41
41
  # people to exclude from update feeds
@@ -86,23 +86,7 @@ module Eco
86
86
  def default_schema?
87
87
  !!self["default_schema"]
88
88
  end
89
-
90
- def presets_custom=(file)
91
- self["presets_custom"] = file
92
- end
93
-
94
- def presets_custom
95
- self["presets_custom"]
96
- end
97
-
98
- def presets_map=(file)
99
- self["presets_map"] = file
100
- end
101
-
102
- def presets_map
103
- self["presets_map"]
104
- end
105
-
89
+
106
90
  # @return [Hash] with defined pairs format `key` and Person parsers.
107
91
  def parsers
108
92
  self["parsers"] ||= {}
@@ -9,6 +9,8 @@ module Eco
9
9
  end
10
10
  end
11
11
 
12
+ require_relative 'default_cases/abstract_policygroup_abilities_case.rb'
13
+ require_relative 'default_cases/analyse_people_case'
12
14
  require_relative 'default_cases/append_usergroups_case'
13
15
  require_relative 'default_cases/change_email_case'
14
16
  require_relative 'default_cases/codes_to_tags_case'
@@ -22,7 +24,6 @@ require_relative 'default_cases/hris_case'
22
24
  require_relative 'default_cases/new_id_case'
23
25
  require_relative 'default_cases/new_email_case'
24
26
  require_relative 'default_cases/org_data_convert_case'
25
- require_relative 'default_cases/refresh_abilities_case'
26
27
  require_relative 'default_cases/refresh_case'
27
28
  require_relative 'default_cases/reinvite_trans_case'
28
29
  require_relative 'default_cases/reinvite_sync_case'
@@ -32,6 +33,8 @@ require_relative 'default_cases/reset_landing_page_case'
32
33
  require_relative 'default_cases/restore_db_case'
33
34
  require_relative 'default_cases/set_default_tag_case'
34
35
  require_relative 'default_cases/set_supervisor_case'
36
+ require_relative 'default_cases/supers_hierarchy_case'
37
+ require_relative 'default_cases/supers_cyclic_identify_case'
35
38
  require_relative 'default_cases/switch_supervisor_case'
36
39
  require_relative 'default_cases/to_csv_case'
37
40
  require_relative 'default_cases/to_csv_detailed_case'
@@ -0,0 +1,161 @@
1
+ class Eco::API::UseCases::DefaultCases::AbstractPolicyGroupAbilities < Eco::API::Common::Loaders::UseCase
2
+ name "abstract-policygroup-abilities"
3
+ type :export
4
+
5
+ attr_reader :session, :people, :options
6
+
7
+ def main(people, session, options, usecase)
8
+ options[:end_get] = false
9
+ @session = session; @options = options; @people = people
10
+
11
+ generate_csv!
12
+ end
13
+
14
+ private
15
+
16
+ def file
17
+ @file ||= options.dig(:output, :file) || "suggested_abilities.csv"
18
+ end
19
+
20
+ def generate_csv!
21
+ policy_groups.each_with_object({}) do |group, data|
22
+ data[group.id] ||= {}
23
+ data[group.id][:name] = group.name
24
+ data[group.id][:suggested] = group_suggestions(group.id)
25
+ data[group.id][:percents] = group_abilities(group.id)
26
+ end.yield_self do |data|
27
+ abilities_list = Eco::API::Organization::PresetsFactory.abilities
28
+ CSV.open(file, "w") do |csv|
29
+ csv << ["Type", "UserGroup", "ID", *abilities_list]
30
+ # Dump the final suggestions
31
+ data.each do |id, meta|
32
+ csv << ["Suggested", meta[:name], id, *meta[:suggested].values_at(*abilities_list)]
33
+ end
34
+ # Dump the percentaged levels of each ability
35
+ data.each do |id, meta|
36
+ analysis = meta[:percents].values_at(*abilities_list).each_with_object([]) do |levels, values|
37
+ values << levels.map do |level, percentil|
38
+ "#{level ? level : "null"} => #{percentil}"
39
+ end.join("\n")
40
+ end
41
+ csv << ["Analysis", meta[:name], id, *analysis]
42
+ end
43
+ end
44
+ puts "Generated file #{file}"
45
+ end
46
+ end
47
+
48
+ def policy_groups
49
+ @policy_groups ||= session.policy_groups
50
+ end
51
+
52
+ # Suggests 1 final set of abilities for a usergroup
53
+ def group_suggestions(id)
54
+ group_abilities(id).each_with_object({}) do |(key, levels), set|
55
+ data = levels.to_a.sort_by(&:last).reverse.first
56
+ set[key] = data ? data.shift : nil
57
+ end
58
+ end
59
+
60
+ # Cleans up each ability's levels by:
61
+ # 1. cutting at some minimum threshold percentage
62
+ # 2. directly selecting those greater than 70%
63
+ def group_abilities(id)
64
+ @group_abilities ||= {}
65
+ @group_abilities[id] ||= ability_levels_percent(id).each_with_object({}) do |(ability, levels), out|
66
+ outstanding = nil
67
+ levels.select do |level, percentil|
68
+ outstanding ||= level if percentil >= 75
69
+ percentil > 15
70
+ end.yield_self do |filtered|
71
+ out[ability] = outstanding ? filtered.slice(outstanding) : filtered
72
+ end
73
+ end
74
+ end
75
+
76
+ # With given the percentages of sets of abilities
77
+ # it abstracts the percentage of each level of each ability
78
+ def ability_levels_percent(id)
79
+ @ability_levels_percent ||= {}
80
+ @ability_levels_percent[id] ||= Eco::API::Organization::PresetsFactory.abilities.each_with_object({}) do |key, out|
81
+ out[key] ||= {}
82
+ ability_sets_percent(id).each_with_object(out[key]) do |(set, percentil), levels|
83
+ levels[set[key]] ||= 0
84
+ levels[set[key]] = (levels[set[key]] + percentil).round(2)
85
+ end
86
+ end
87
+ end
88
+
89
+ # Give a percentage to each set of abilities
90
+ def ability_sets_percent(id)
91
+ @ability_sets_percent ||= {}
92
+ @ability_sets_percent[id] ||= scoped_relevant_raw_data(id).yield_self do |data|
93
+ # Transform ability sets counter to percentage
94
+ total = data[:count]
95
+ abilities = data[:abilities]
96
+ data[:abilities].transform_values do |val|
97
+ percent(val, total)
98
+ end
99
+ end
100
+ end
101
+
102
+ # Get rid of data simingly irrelevant
103
+ def scoped_relevant_raw_data(id)
104
+ sp = single_percent(id)
105
+ sing = single(id); mult = multiple(id)
106
+
107
+ # Scope Relevant Raw Data
108
+ case
109
+ when sp >= 3 # >= 80
110
+ sing
111
+ #when sp < 15
112
+ # mult
113
+ else # combine
114
+ all_abilities = sing[:abilities].keys | mult[:abilities].keys
115
+ data = {count: sing[:count] + mult[:count], abilities: {}}
116
+ all_abilities.each_with_object(data) do |abilities, merged|
117
+ scount = sing[:abilities][abilities] || 0
118
+ mcount = mult[:abilities][abilities] || 0
119
+ merged[:abilities][abilities] = scount + mcount
120
+ end
121
+ end
122
+ end
123
+
124
+ def single_percent(id)
125
+ percent(single(id)[:count], count(id))
126
+ end
127
+
128
+ def single(id)
129
+ groups_abilities.dig(id, :single) || {count: 0, abilities: {}}
130
+ end
131
+
132
+ def multiple(id)
133
+ groups_abilities.dig(id, :multiple) || {count: 0, abilities: {}}
134
+ end
135
+
136
+ def count(id)
137
+ groups_abilities.dig(id, :count) || 0
138
+ end
139
+
140
+ def groups_abilities
141
+ @groups_abilities ||= people.users.each_with_object({}) do |user, groups|
142
+ abilities = Eco::API::Organization::PresetsFactory.all_abilities(user.account.permissions_custom)
143
+ ids = user.account.policy_group_ids
144
+ category = ids.count > 1 ? :multiple : :single
145
+
146
+ ids.each do |id|
147
+ groups[id] ||= {count: 0}
148
+ groups[id][:count] += 1
149
+ groups[id][category] ||= {count: 0, abilities: {}}
150
+ groups[id][category][:count] += 1
151
+ groups[id][category][:abilities][abilities] ||= 0
152
+ groups[id][category][:abilities][abilities] += 1
153
+ end
154
+ end
155
+ end
156
+
157
+ def percent(num, total)
158
+ (100 * num.to_f / total).round(2)
159
+ end
160
+
161
+ end
@@ -0,0 +1,53 @@
1
+ class Eco::API::UseCases::DefaultCases::AnalysePeople < Eco::API::Common::Loaders::UseCase
2
+ name "analyse-people"
3
+ type :export
4
+
5
+ attr_reader :session, :people, :options
6
+
7
+ def main(people, session, options, usecase)
8
+ @session = session; @options = options; @people = people
9
+
10
+ save!(analysis)
11
+ end
12
+
13
+ private
14
+
15
+ def analysis
16
+ analytics.attribute = :name
17
+ analysed = analytics.analyse({
18
+ threshold: 0.20,
19
+ order: [:average, :dice]
20
+ })
21
+ analytics.re_analyse(analysed, threshold: 0.5, order: [:average])
22
+ end
23
+
24
+ def analytics
25
+ @analytics ||= people.analytics
26
+ end
27
+
28
+ def file
29
+ @file ||= options.dig(:output, :file) || "analytics.txt"
30
+ end
31
+
32
+ def save!(analysed)
33
+ if analysed.empty?
34
+ session.logger.info("There were results identified!!")
35
+ return
36
+ end
37
+
38
+ ext = File.extname(file).downcase.delete(".")
39
+
40
+ File.open(file, "w") do |fd|
41
+ if ext == "txt"
42
+ fd << analytics.analysis(analysed, format: :txt)
43
+ elsif ext == "html"
44
+ puts "html is still not supported"
45
+ exit(1)
46
+ elsif ext == "json"
47
+ puts "json is still not supported"
48
+ exit(1)
49
+ end
50
+ end
51
+ end
52
+
53
+ end