eco-helpers 2.0.12 → 2.0.17

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -73
  3. data/eco-helpers.gemspec +6 -4
  4. data/lib/eco-helpers.rb +1 -0
  5. data/lib/eco/api/common/base_loader.rb +14 -0
  6. data/lib/eco/api/common/loaders/use_case.rb +1 -1
  7. data/lib/eco/api/common/people/default_parsers/date_parser.rb +11 -1
  8. data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +1 -1
  9. data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +11 -11
  10. data/lib/eco/api/common/people/person_entry.rb +9 -2
  11. data/lib/eco/api/common/people/supervisor_helpers.rb +27 -0
  12. data/lib/eco/api/common/session/file_manager.rb +2 -2
  13. data/lib/eco/api/common/session/mailer.rb +0 -1
  14. data/lib/eco/api/common/session/s3_uploader.rb +0 -1
  15. data/lib/eco/api/common/session/sftp.rb +0 -1
  16. data/lib/eco/api/error.rb +5 -3
  17. data/lib/eco/api/microcases.rb +3 -1
  18. data/lib/eco/api/microcases/append_usergroups.rb +0 -1
  19. data/lib/eco/api/microcases/people_cache.rb +2 -2
  20. data/lib/eco/api/microcases/people_load.rb +2 -2
  21. data/lib/eco/api/microcases/people_refresh.rb +2 -2
  22. data/lib/eco/api/microcases/people_search.rb +6 -6
  23. data/lib/eco/api/microcases/preserve_default_tag.rb +23 -0
  24. data/lib/eco/api/microcases/preserve_filter_tags.rb +28 -0
  25. data/lib/eco/api/microcases/preserve_policy_groups.rb +30 -0
  26. data/lib/eco/api/microcases/set_account.rb +0 -1
  27. data/lib/eco/api/organization.rb +1 -0
  28. data/lib/eco/api/organization/people.rb +7 -0
  29. data/lib/eco/api/organization/people_analytics.rb +60 -0
  30. data/lib/eco/api/organization/presets_factory.rb +116 -93
  31. data/lib/eco/api/organization/presets_integrity.json +58 -0
  32. data/lib/eco/api/organization/presets_values.json +5 -4
  33. data/lib/eco/api/policies/default_policies/99_user_access_policy.rb +0 -30
  34. data/lib/eco/api/session.rb +1 -20
  35. data/lib/eco/api/session/batch.rb +42 -10
  36. data/lib/eco/api/session/batch/job.rb +3 -0
  37. data/lib/eco/api/session/config.rb +16 -15
  38. data/lib/eco/api/session/config/api.rb +4 -0
  39. data/lib/eco/api/session/config/apis.rb +14 -0
  40. data/lib/eco/api/session/config/files.rb +7 -0
  41. data/lib/eco/api/session/config/people.rb +3 -19
  42. data/lib/eco/api/usecases.rb +2 -0
  43. data/lib/eco/api/usecases/default_cases.rb +4 -1
  44. data/lib/eco/api/usecases/default_cases/abstract_policygroup_abilities_case.rb +161 -0
  45. data/lib/eco/api/usecases/default_cases/analyse_people_case.rb +76 -0
  46. data/lib/eco/api/usecases/default_cases/codes_to_tags_case.rb +2 -3
  47. data/lib/eco/api/usecases/default_cases/hris_case.rb +14 -8
  48. data/lib/eco/api/usecases/default_cases/reset_landing_page_case.rb +11 -1
  49. data/lib/eco/api/usecases/default_cases/restore_db_case.rb +1 -2
  50. data/lib/eco/api/usecases/default_cases/supers_cyclic_identify_case.rb +72 -0
  51. data/lib/eco/api/usecases/default_cases/supers_hierarchy_case.rb +59 -0
  52. data/lib/eco/api/usecases/default_cases/to_csv_case.rb +104 -26
  53. data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +62 -36
  54. data/lib/eco/cli.rb +0 -10
  55. data/lib/eco/cli/config/default/options.rb +19 -17
  56. data/lib/eco/cli/config/default/people_filters.rb +3 -3
  57. data/lib/eco/cli/config/default/usecases.rb +77 -25
  58. data/lib/eco/cli/config/default/workflow.rb +6 -1
  59. data/lib/eco/cli/config/help.rb +1 -0
  60. data/lib/eco/cli/config/options_set.rb +106 -13
  61. data/lib/eco/cli/config/use_cases.rb +33 -33
  62. data/lib/eco/cli/scripting/args_helpers.rb +30 -3
  63. data/lib/eco/data.rb +1 -0
  64. data/lib/eco/data/crypto/encryption.rb +3 -3
  65. data/lib/eco/data/files/directory.rb +28 -20
  66. data/lib/eco/data/files/helpers.rb +6 -4
  67. data/lib/eco/data/fuzzy_match.rb +119 -0
  68. data/lib/eco/data/fuzzy_match/array_helpers.rb +75 -0
  69. data/lib/eco/data/fuzzy_match/chars_position_score.rb +37 -0
  70. data/lib/eco/data/fuzzy_match/ngrams_score.rb +73 -0
  71. data/lib/eco/data/fuzzy_match/pairing.rb +102 -0
  72. data/lib/eco/data/fuzzy_match/result.rb +67 -0
  73. data/lib/eco/data/fuzzy_match/results.rb +53 -0
  74. data/lib/eco/data/fuzzy_match/score.rb +44 -0
  75. data/lib/eco/data/fuzzy_match/stop_words.rb +35 -0
  76. data/lib/eco/data/fuzzy_match/string_helpers.rb +69 -0
  77. data/lib/eco/version.rb +1 -1
  78. metadata +86 -10
  79. data/lib/eco/api/microcases/refresh_abilities.rb +0 -19
  80. data/lib/eco/api/organization/presets_reference.json +0 -59
  81. data/lib/eco/api/usecases/default_cases/refresh_abilities_case.rb +0 -30
@@ -20,7 +20,7 @@ class Eco::API::Common::People::DefaultParsers::LoginProvidersParser < Eco::API:
20
20
  private
21
21
 
22
22
  def login_providers
23
- @login_providers ||= ASSETS.config.login_providers
23
+ @login_providers ||= config.login_providers
24
24
  end
25
25
 
26
26
  end
@@ -1,31 +1,31 @@
1
1
  class Eco::API::Common::People::DefaultParsers::PolicyGroupsParser < Eco::API::Common::Loaders::Parser
2
2
  attribute "policy_group_ids"
3
+ parsing_phase :final
3
4
 
4
5
  def parser(hash, deps)
5
- policy_group_ids = []
6
- if policy_ids = hash["policy_group_ids"]
7
- policy_group_ids = policy_ids.split("|").map do |name|
8
- policy_groups.to_id(name&.downcase.strip)
9
- end.compact
6
+ policy_group_ids = hash["policy_group_ids"] || []
7
+ policy_group_ids.map do |name|
8
+ policy_groups.to_id(name&.downcase.strip)
9
+ end.compact.tap do |pg_names|
10
+ pg_names.push(default_id) if pg_names.empty?
10
11
  end
11
- policy_group_ids.empty?? default_id : policy_group_ids.join("|")
12
12
  end
13
13
 
14
14
  def serializer(person, deps)
15
15
  ids = person&.account&.policy_group_ids || []
16
16
  ids.map do |id|
17
17
  policy_groups.to_name(id)
18
- end.compact.join("|")
18
+ end.compact
19
19
  end
20
20
 
21
21
  private
22
22
 
23
- def policy_groups
24
- @policy_groups ||= ASSETS.config.policy_groups
23
+ def default_id
24
+ @default_id ||= policy_groups.to_id(config.people.default_usergroup)
25
25
  end
26
26
 
27
- def default_id
28
- @default_id ||= policy_groups.to_id(ASSETS.config.people.default_usergroup)
27
+ def policy_groups
28
+ @policy_groups ||= config.policy_groups
29
29
  end
30
30
 
31
31
  end
@@ -57,6 +57,11 @@ module Eco
57
57
  @internal_entry
58
58
  end
59
59
 
60
+ # @return [Hash] entry `Hash` with **internal** attribute names, but **external** types and values.
61
+ def mapped_entry
62
+ @mapped_entry
63
+ end
64
+
60
65
  # @note values ready to be set to a person.
61
66
  # @return [Hash] entry `Hash` with **internal** attribute names, values and types.
62
67
  def final_entry
@@ -205,7 +210,6 @@ module Eco
205
210
  # @param exclude [String, Array<String>] account properties that should not be set/changed to the person.
206
211
  def set_account(person, exclude: nil)
207
212
  person.account = {} if !person.account
208
- person.account.permissions_preset = nil unless person.account.permissions_preset = "custom"
209
213
  scoped_attrs = @emap.account_attrs - into_a(exclude)
210
214
  @final_entry.slice(*scoped_attrs).each do |attr, value|
211
215
  set_part(person.account, attr, value)
@@ -285,7 +289,10 @@ module Eco
285
289
  # @param internal_entry [Hash] entry with **internal** names and values, but **external** types.
286
290
  # @return [Hash] entry with **internal** names and **external** values and types.
287
291
  def _mapped_serializing(internal_entry)
288
- internal_entry.merge(_serialize_values(internal_entry, :internal))
292
+ mapped_hash = internal_entry.merge(_serialize_values(internal_entry, :internal))
293
+ model_attrs = @person_parser.all_model_attrs - ["send_invites"]
294
+ aux_hash = mapped_hash.slice(*model_attrs)
295
+ merge_missing_attrs(aux_hash, mapped_hash)
289
296
  end
290
297
 
291
298
  # Parsing helper that just **parses the values** that have a parser/serializer defined.
@@ -15,6 +15,7 @@ module Eco
15
15
  # Reorders as follows:
16
16
  # 1. supervisors, people with no supervisor or where their supervisor not present
17
17
  # 2. subordinates
18
+ # @return [Array<Entry>] `values` sorted by supervisors/subordinates
18
19
  def sort_by_supervisors(values, supervisors_first: true)
19
20
  raise "Expected non hash Enumerable. Given: #{values.class}" if values.is_a?(Hash)
20
21
  return [] unless values && values.is_a?(Enumerable)
@@ -32,6 +33,32 @@ module Eco
32
33
  roam.call(supervisors_tree(values))
33
34
  end
34
35
 
36
+ # Identifies all the cyclic supervisor chains
37
+ # @note as `supervisors_tree` will have any entry involved in a cycle at the top, it just checks all the top entries against their offspring
38
+ # @return [Array<Array>] the sets of entries that are cyclic
39
+ def identify_cyclic_chains(values)
40
+ raise "Expected non hash Enumerable. Given: #{values.class}" if values.is_a?(Hash)
41
+ return [] unless values && values.is_a?(Enumerable)
42
+
43
+ identify = Proc.new do |top_sup, offspring, chain = [top_sup]|
44
+ next [] if offspring.empty?
45
+ offspring.each_with_object([]) do |(sup, subordinates), set|
46
+ break set unless set.empty?
47
+ if top_sup.supervisor_id == sup.id
48
+ set.concat(chain, [sup])
49
+ else
50
+ set = identify.call(top_sup, subordinates, chain | [sup])
51
+ end
52
+ end
53
+ end
54
+
55
+ supervisors_tree(values).each_with_object([]) do |(top_sup, offspring), sets|
56
+ if (set = identify.call(top_sup, offspring)) && !set.empty?
57
+ sets.push(set)
58
+ end
59
+ end
60
+ end
61
+
35
62
  def tree_to_str(tree, lev: 0)
36
63
  raise "Required Hash tree structure. Given: #{tree.class}" unless tree.is_a?(Hash)
37
64
  "".tap do |str|
@@ -20,8 +20,8 @@ module Eco
20
20
  begin
21
21
  @dir = Eco::Data::Files::Directory.new(value)
22
22
  @dir_path = @dir.create
23
- rescue
24
- logger.error("could not create or make any sense of directory '#{value}'")
23
+ rescue Exception => e
24
+ logger.error("could not create or make any sense of directory '#{value}': #{e.to_s}")
25
25
  end
26
26
  end
27
27
 
@@ -1,5 +1,4 @@
1
1
  require 'aws-sdk-ses'
2
- require 'dotenv/load'
3
2
 
4
3
  module Eco
5
4
  module API
@@ -1,5 +1,4 @@
1
1
  require 'aws-sdk-s3'
2
- require 'dotenv/load'
3
2
 
4
3
  module Eco
5
4
  module API
@@ -1,5 +1,4 @@
1
1
  require "net/sftp"
2
- require 'dotenv/load'
3
2
 
4
3
  module Eco
5
4
  module API
data/lib/eco/api/error.rb CHANGED
@@ -80,6 +80,7 @@ module Eco
80
80
  next 1 if k1 < k2
81
81
  0
82
82
  end.tap do |siblings|
83
+ siblings.delete(Unclassified)
83
84
  if direct
84
85
  siblings.reject! do |si|
85
86
  siblings.any? {|s| si < s}
@@ -96,17 +97,18 @@ module Eco
96
97
  err_msg =~ @match
97
98
  end
98
99
 
99
- def get_type(err_msg)
100
+ def get_type(err_msg, first: true)
100
101
  type = nil
101
102
  descendants(direct: true).reverse.each do |klass|
102
103
  if klass.err_match?(err_msg)
103
104
  type = klass
104
105
  if klass.descendants?(direct: true)
105
- type = klass.get_type(err_msg) || type
106
+ type = klass.get_type(err_msg, first: false) || type
106
107
  end
107
108
  end
108
109
  end
109
- type
110
+ return type unless first
111
+ type || Unclassified
110
112
  end
111
113
 
112
114
  def known_err_class?(klass)
@@ -19,7 +19,9 @@ require_relative 'microcases/people_cache'
19
19
  require_relative 'microcases/people_load'
20
20
  require_relative 'microcases/people_refresh'
21
21
  require_relative 'microcases/people_search'
22
- require_relative 'microcases/refresh_abilities'
22
+ require_relative 'microcases/preserve_filter_tags'
23
+ require_relative 'microcases/preserve_default_tag'
24
+ require_relative 'microcases/preserve_policy_groups'
23
25
  require_relative 'microcases/set_account'
24
26
  require_relative 'microcases/set_core_with_supervisor'
25
27
  require_relative 'microcases/set_core'
@@ -9,7 +9,6 @@ module Eco
9
9
  unless options.dig(:exclude, :account)
10
10
  if person.account
11
11
  person.account.policy_group_ids |= entry.policy_group_ids
12
- micro.refresh_abilities(person, options)
13
12
  end
14
13
  end
15
14
  end
@@ -9,9 +9,9 @@ module Eco
9
9
 
10
10
  start = Time.now
11
11
  people = session.batch.get_people
12
- secs = Time.now - start
12
+ secs = (Time.now - start).round(3)
13
13
  cnt = people.count
14
- per_sec = (cnt.to_f / secs).floor
14
+ per_sec = (cnt.to_f / secs).round(2)
15
15
  logger.info("Loaded #{cnt} people in #{secs} seconds (#{per_sec} people/sec)")
16
16
 
17
17
  file = file_manager.save_json(people, filename, :timestamp)
@@ -34,9 +34,9 @@ module Eco
34
34
 
35
35
  start = Time.now
36
36
  session.batch.get_people.tap do |people|
37
- secs = Time.now - start
37
+ secs = (Time.now - start).round(3)
38
38
  cnt = people.count
39
- per_sec = (cnt.to_f / secs).floor
39
+ per_sec = (cnt.to_f / secs).round(2)
40
40
  logger.info("Loaded #{cnt} people in #{secs} seconds (#{per_sec} people/sec)")
41
41
 
42
42
  if modifier.include?(:save) && people && people.length > 0
@@ -21,9 +21,9 @@ module Eco
21
21
 
22
22
  start = Time.now
23
23
  entries = session.batch.get_people(people, silent: true)
24
- secs = Time.now - start
24
+ secs = (Time.now - start).round(3)
25
25
  cnt = entries.count
26
- per_sec = (cnt.to_f / secs).floor
26
+ per_sec = (cnt.to_f / secs).round(2)
27
27
  logger.info("Re-loaded #{cnt} people (out of #{people.length}) in #{secs} seconds (#{per_sec} people/sec)")
28
28
 
29
29
  missing = people.length - entries.length
@@ -14,10 +14,10 @@ module Eco
14
14
 
15
15
  start = Time.now
16
16
  people = session.batch.search(data, silent: silent).yield_self do |status|
17
- secs = Time.now - start
17
+ secs = (Time.now - start).round(3)
18
18
  Eco::API::Organization::People.new(status.people).tap do |people|
19
19
  cnt = people.count
20
- per_sec = (cnt.to_f / secs).floor
20
+ per_sec = (cnt.to_f / secs).round(2)
21
21
  msg = "... could get #{cnt} people (out of #{data.length} entries) in #{secs} seconds (#{per_sec} people/sec)"
22
22
  session.logger.info(msg)
23
23
  end
@@ -29,10 +29,10 @@ module Eco
29
29
  session.logger.info(" Going to api get #{supers.length} current supervisors...")
30
30
  start = Time.now
31
31
  people = session.batch.search(supers, silent: silent).yield_self do |status|
32
- secs = Time.now - start
32
+ secs = (Time.now - start).round(3)
33
33
  found = status.people
34
34
  cnt = found.count
35
- per_sec = (cnt.to_f / secs).floor
35
+ per_sec = (cnt.to_f / secs).round(2)
36
36
  msg = "... could find #{cnt} current supers (out of #{supers.length}) in #{secs} seconds (#{per_sec} people/sec)"
37
37
  session.logger.info(msg)
38
38
  people.merge(found, strict: micro.strict_search?(options))
@@ -46,10 +46,10 @@ module Eco
46
46
  start = Time.now
47
47
 
48
48
  people = session.batch.search(supers, silent: silent).yield_self do |status|
49
- secs = Time.now - start
49
+ secs = (Time.now - start).round(3)
50
50
  found = status.people
51
51
  cnt = found.count
52
- per_sec = (cnt.to_f / secs).floor
52
+ per_sec = (cnt.to_f / secs).round(2)
53
53
  msg = "... could find #{cnt} input supers (out of #{supers.length}) in #{secs} seconds (#{per_sec} people/sec)"
54
54
  session.logger.info(msg)
55
55
  people.merge(found, strict: micro.strict_search?(options))
@@ -0,0 +1,23 @@
1
+ module Eco
2
+ module API
3
+ class MicroCases
4
+ # Helper to preserve the original `default_tag`.
5
+ # @note
6
+ # 1. It only works if the original value of `default_tag` was **not** empty
7
+ # @param person [Ecoportal::API::V1::Person] the person we want to update, carrying the changes to be done.
8
+ # @param options [Hash] the options.
9
+ # @return [String] the final value of `default_tag`.
10
+ def preserve_default_tag(person, options)
11
+ if account = person.account
12
+ if account.as_update.key?("default_tag")
13
+ if original = person.original_doc.dig("account", "default_tag")
14
+ person.account.default_tag = original
15
+ end
16
+ end
17
+ end
18
+ person.account&.default_tag
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ module Eco
2
+ module API
3
+ class MicroCases
4
+ # Helper to preserve the original filter tags.
5
+ # @note
6
+ # 1. It only works if the original value of `filter_tags` was **not** empty
7
+ # @param person [Ecoportal::API::V1::Person] the person we want to update, carrying the changes to be done.
8
+ # @param options [Hash] the options.
9
+ # @param keep_new [Boolean] tells if it should keep the new tags or get rid of them.
10
+ # @return [Array<String>] the final value of `filter_tags`.
11
+ def preserve_filter_tags(person, options, keep_new: false)
12
+ if person.as_update.key?("filter_tags")
13
+ if original = person.original_doc["filter_tags"]
14
+ unless original.empty?
15
+ if keep_new
16
+ person.filter_tags += original
17
+ else
18
+ person.filter_tags = original
19
+ end
20
+ end
21
+ end
22
+ end
23
+ person.filter_tags
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module Eco
2
+ module API
3
+ class MicroCases
4
+ # Helper to preserve the original `policy_group_ids`.
5
+ # @note
6
+ # 1. It only works if the original value of `policy_group_ids` was **not** empty
7
+ # @param person [Ecoportal::API::V1::Person] the person we want to update, carrying the changes to be done.
8
+ # @param options [Hash] the options.
9
+ # @param keep_new [Boolean] tells if it should keep the new policy groups or get rid of them.
10
+ # @return [String] the final value of `policy_group_ids`.
11
+ def preserve_policy_groups(person, options, keep_new: false)
12
+ if account = person.account
13
+ if account.as_update.key?("policy_group_ids")
14
+ if original = person.original_doc.dig("account", "policy_group_ids")
15
+ unless original.empty?
16
+ if keep_new
17
+ person.account.policy_group_ids += original
18
+ else
19
+ person.account.policy_group_ids = original
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ person.account&.policy_group_ids
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -11,7 +11,6 @@ module Eco
11
11
  person.account.send_invites = options[:send_invites] if options.key?(:send_invites)
12
12
  micro.refresh_default_tag(entry, person, options)
13
13
  micro.fix_default_group(entry, person, options)
14
- micro.refresh_abilities(person, options)
15
14
  end
16
15
  end
17
16
 
@@ -9,6 +9,7 @@ require_relative 'organization/tag_tree'
9
9
  require_relative 'organization/presets_factory'
10
10
  require_relative 'organization/preferences'
11
11
  require_relative 'organization/people'
12
+ require_relative 'organization/people_analytics'
12
13
  require_relative 'organization/person_schemas'
13
14
  require_relative 'organization/policy_groups'
14
15
  require_relative 'organization/login_providers'
@@ -176,6 +176,12 @@ module Eco
176
176
  end
177
177
  # @!endgroup
178
178
 
179
+ # @!group Helper methods
180
+ def analytics
181
+ Eco::API::Organization::PeopleAnalytics.new(self.to_a)
182
+ end
183
+ # @!endgroup
184
+
179
185
  protected
180
186
 
181
187
  def on_change
@@ -190,6 +196,7 @@ module Eco
190
196
  @by_external_id = to_h('external_id')
191
197
  @by_users_email = users.to_h('email')
192
198
  @by_non_users_email = non_users.to_h('email')
199
+ @by_email = to_h('email')
193
200
  @caches_init = true
194
201
  end
195
202
 
@@ -0,0 +1,60 @@
1
+ module Eco
2
+ module API
3
+ module Organization
4
+ class PeopleAnalytics < Eco::API::Organization::People
5
+ include Eco::Data::FuzzyMatch
6
+
7
+ # @!group Helpers
8
+
9
+ # @!endgroup
10
+
11
+ # @!group Searchers
12
+
13
+ # It gathers those that have the same `email`
14
+ # @return [Hash] where `keys` are `email`s and `values` an `Array<Person>`
15
+ def repeated_emails
16
+ init_caches
17
+ @by_email.select do |email, people|
18
+ people.count > 1
19
+ end
20
+ end
21
+
22
+ # @!endgroup
23
+
24
+ # @!group Analysers
25
+
26
+ # TODO: Sort results by `results.first.methods`
27
+ def similarity(**options)
28
+ each_with_object({}) do |person, results|
29
+ results[person.id] = find_all_with_score(person, **options)
30
+ end
31
+ end
32
+
33
+
34
+ def print_analysis(threshold)
35
+ similarity.each do |id, results|
36
+ msg = results.results.select do |result|
37
+ result.threshold?(threshold)
38
+ end.map do |result|
39
+ result.print
40
+ end.join("\n ")
41
+
42
+ puts "'#{self[id].identify}':\n " + msg
43
+ end
44
+ end
45
+ # @!endgroup
46
+
47
+ protected
48
+
49
+ def on_change
50
+ remove_instance_variable(@fuzzy_match)
51
+ super
52
+ end
53
+
54
+ private
55
+
56
+
57
+ end
58
+ end
59
+ end
60
+ end