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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +90 -2
- data/eco-helpers.gemspec +6 -4
- data/lib/eco-helpers.rb +2 -0
- data/lib/eco/api/common/base_loader.rb +14 -0
- data/lib/eco/api/common/people/default_parsers/date_parser.rb +11 -1
- data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +1 -1
- data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +11 -11
- data/lib/eco/api/common/people/entry_factory.rb +26 -9
- data/lib/eco/api/common/people/person_entry.rb +5 -2
- data/lib/eco/api/common/people/supervisor_helpers.rb +27 -0
- data/lib/eco/api/common/session.rb +1 -0
- data/lib/eco/api/common/session/base_session.rb +2 -0
- data/lib/eco/api/common/session/file_manager.rb +2 -2
- data/lib/eco/api/common/session/helpers.rb +30 -0
- data/lib/eco/api/common/session/helpers/prompt_user.rb +34 -0
- data/lib/eco/api/common/session/mailer.rb +0 -1
- data/lib/eco/api/common/session/s3_uploader.rb +0 -1
- data/lib/eco/api/common/session/sftp.rb +0 -1
- data/lib/eco/api/common/version_patches/ecoportal_api/external_person.rb +1 -1
- data/lib/eco/api/common/version_patches/ecoportal_api/internal_person.rb +7 -4
- data/lib/eco/api/common/version_patches/exception.rb +8 -4
- data/lib/eco/api/microcases.rb +3 -1
- data/lib/eco/api/microcases/append_usergroups.rb +0 -1
- data/lib/eco/api/microcases/people_cache.rb +2 -2
- data/lib/eco/api/microcases/people_load.rb +2 -2
- data/lib/eco/api/microcases/people_refresh.rb +2 -2
- data/lib/eco/api/microcases/people_search.rb +6 -6
- data/lib/eco/api/microcases/preserve_default_tag.rb +23 -0
- data/lib/eco/api/microcases/preserve_filter_tags.rb +28 -0
- data/lib/eco/api/microcases/preserve_policy_groups.rb +30 -0
- data/lib/eco/api/microcases/set_account.rb +0 -1
- data/lib/eco/api/microcases/with_each.rb +67 -6
- data/lib/eco/api/microcases/with_each_present.rb +4 -2
- data/lib/eco/api/microcases/with_each_starter.rb +4 -2
- data/lib/eco/api/organization.rb +1 -0
- data/lib/eco/api/organization/people.rb +98 -22
- data/lib/eco/api/organization/people_similarity.rb +112 -0
- data/lib/eco/api/organization/person_schemas.rb +5 -1
- data/lib/eco/api/organization/policy_groups.rb +5 -1
- data/lib/eco/api/organization/presets_factory.rb +40 -80
- data/lib/eco/api/organization/presets_integrity.json +6 -0
- data/lib/eco/api/organization/presets_values.json +5 -4
- data/lib/eco/api/policies/default_policies/99_user_access_policy.rb +0 -30
- data/lib/eco/api/session.rb +6 -22
- data/lib/eco/api/session/batch.rb +25 -7
- data/lib/eco/api/session/config.rb +16 -15
- data/lib/eco/api/session/config/api.rb +4 -0
- data/lib/eco/api/session/config/apis.rb +80 -0
- data/lib/eco/api/session/config/files.rb +7 -0
- data/lib/eco/api/session/config/people.rb +3 -19
- data/lib/eco/api/usecases/default_cases.rb +4 -1
- data/lib/eco/api/usecases/default_cases/abstract_policygroup_abilities_case.rb +161 -0
- data/lib/eco/api/usecases/default_cases/analyse_people_case.rb +53 -0
- data/lib/eco/api/usecases/default_cases/codes_to_tags_case.rb +2 -3
- data/lib/eco/api/usecases/default_cases/reset_landing_page_case.rb +11 -1
- data/lib/eco/api/usecases/default_cases/restore_db_case.rb +1 -2
- 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 +132 -29
- data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +61 -36
- data/lib/eco/api/usecases/ooze_samples/ooze_update_case.rb +3 -2
- data/lib/eco/cli.rb +0 -10
- data/lib/eco/cli/config/default/options.rb +20 -17
- data/lib/eco/cli/config/default/people_filters.rb +3 -3
- data/lib/eco/cli/config/default/usecases.rb +80 -26
- data/lib/eco/cli/config/default/workflow.rb +16 -4
- data/lib/eco/cli/config/help.rb +1 -0
- data/lib/eco/cli/config/options_set.rb +106 -13
- data/lib/eco/cli/config/use_cases.rb +33 -33
- data/lib/eco/cli/scripting/args_helpers.rb +30 -3
- data/lib/eco/csv.rb +4 -2
- data/lib/eco/data.rb +1 -0
- data/lib/eco/data/crypto/encryption.rb +3 -3
- data/lib/eco/data/files/directory.rb +28 -20
- data/lib/eco/data/files/helpers.rb +6 -4
- data/lib/eco/data/fuzzy_match.rb +161 -0
- data/lib/eco/data/fuzzy_match/array_helpers.rb +75 -0
- data/lib/eco/data/fuzzy_match/chars_position_score.rb +37 -0
- data/lib/eco/data/fuzzy_match/ngrams_score.rb +78 -0
- data/lib/eco/data/fuzzy_match/pairing.rb +101 -0
- data/lib/eco/data/fuzzy_match/result.rb +73 -0
- data/lib/eco/data/fuzzy_match/results.rb +59 -0
- data/lib/eco/data/fuzzy_match/score.rb +44 -0
- data/lib/eco/data/fuzzy_match/stop_words.rb +35 -0
- data/lib/eco/data/fuzzy_match/string_helpers.rb +69 -0
- data/lib/eco/version.rb +1 -1
- metadata +87 -10
- data/lib/eco/api/microcases/refresh_abilities.rb +0 -19
- data/lib/eco/api/organization/presets_reference.json +0 -59
- data/lib/eco/api/usecases/default_cases/refresh_abilities_case.rb +0 -30
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
module Eco
|
3
|
+
module API
|
4
|
+
module Common
|
5
|
+
module Session
|
6
|
+
module Helpers
|
7
|
+
module PromptUser
|
8
|
+
|
9
|
+
def prompt_user(question, default:, explanation: "", timeout: nil)
|
10
|
+
response = if config.run_mode_remote?
|
11
|
+
default
|
12
|
+
else
|
13
|
+
puts explanation
|
14
|
+
print "#{question} "
|
15
|
+
if timeout
|
16
|
+
begin
|
17
|
+
Timeout::timeout(timeout) { STDIN.gets.chop }
|
18
|
+
rescue Timeout::Error
|
19
|
+
default
|
20
|
+
end
|
21
|
+
else
|
22
|
+
STDIN.gets.chop
|
23
|
+
end
|
24
|
+
end
|
25
|
+
return response unless block_given?
|
26
|
+
yield(response)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -11,12 +11,15 @@ module Ecoportal
|
|
11
11
|
original_doc["account"] = JSON.parse(doc["account"])
|
12
12
|
end
|
13
13
|
|
14
|
-
def new?
|
15
|
-
|
14
|
+
def new?(doc = :initial)
|
15
|
+
ref_doc = (doc == :original) ? original_doc : initial_doc
|
16
|
+
!ref_doc["details"] && !ref_doc["account"]
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
# @return [Boolean] if the account has been added, compared to `doc`
|
20
|
+
def account_added?(doc = :initial)
|
21
|
+
ref_doc = (doc == :original) ? original_doc : initial_doc
|
22
|
+
account && !ref_doc["account"]
|
20
23
|
end
|
21
24
|
|
22
25
|
end
|
@@ -1,9 +1,13 @@
|
|
1
1
|
class ::Exception
|
2
2
|
def patch_full_message
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
begin
|
4
|
+
msg = []
|
5
|
+
msg << "\n#{backtrace.first} \n#{message} (#{self.class.to_s})"
|
6
|
+
backtrace[1..-1].each_with_index {|bt, i| msg << "#{" "*8}#{i+1}: from #{bt}"}
|
7
|
+
msg.join("\n")
|
8
|
+
rescue Exception => e
|
9
|
+
puts "Something is wrong with 'patch_full_message': #{e}"
|
10
|
+
end
|
7
11
|
end
|
8
12
|
end
|
9
13
|
|
data/lib/eco/api/microcases.rb
CHANGED
@@ -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/
|
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,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).
|
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).
|
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).
|
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).
|
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).
|
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).
|
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
|
|
@@ -12,16 +12,77 @@ module Eco
|
|
12
12
|
# @yieldparam person [Ecoportal::API::V1::Person] the found person that matches `entry`, or a new person otherwise.
|
13
13
|
# @return [Eco::API::Organization::People] all the people, including new and existing ones.
|
14
14
|
def with_each(entries, people, options)
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
@_skip_all_multiple_results = false
|
16
|
+
entries.each_with_object([]) do |entry, scoped|
|
17
|
+
begin
|
18
|
+
unless person = people.find(entry, strict: micro.strict_search?(options))
|
19
|
+
person = session.new_person
|
20
|
+
end
|
21
|
+
rescue Eco::API::Organization::People::MultipleSearchResults => e
|
22
|
+
unless @_skip_all_multiple_results
|
23
|
+
msg = "\n * When searching this Entry: #{entry.to_s(:identify)}"
|
24
|
+
person = _with_each_prompt_to_select_user(e.append_message(msg), entry: entry)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if person
|
29
|
+
person.entry = entry
|
30
|
+
yield(entry, person) if block_given?
|
31
|
+
scoped << person
|
18
32
|
end
|
19
|
-
person.entry = entry
|
20
|
-
yield(entry, person) if block_given?
|
21
|
-
person
|
22
33
|
end.yield_self {|all_people| people.newFrom all_people.uniq}
|
23
34
|
end
|
24
35
|
|
36
|
+
private
|
37
|
+
|
38
|
+
def _with_each_prompt_to_select_user(error, entry: nil, increase_count: true)
|
39
|
+
unless error.is_a?(Eco::API::Organization::People::MultipleSearchResults)
|
40
|
+
raise "Expecting Eco::API::Organization::People::MultipleSearchResults. Given: #{error.class}"
|
41
|
+
end
|
42
|
+
@_with_each_prompts = 0 unless instance_variable_defined?(:@_with_each_prompts)
|
43
|
+
@_with_each_prompts += 1 if increase_count
|
44
|
+
|
45
|
+
lines = []
|
46
|
+
lines << "\n(#{@_with_each_prompts}) " + error.to_s + "\n"
|
47
|
+
lines << " #index - Select the correct person by its number index among the list above."
|
48
|
+
lines << " (I) - Just Skip/Ignore this one. I will deal with that input entry in another launch."
|
49
|
+
lines << " (A) - Ignore all the rest of input entries with this problem."
|
50
|
+
lines << " (C) - Create a new person."
|
51
|
+
lines << " (B) - Just break this script. I need to change the input file :/"
|
52
|
+
|
53
|
+
prompt_user("Type one option (#number/I/A/C/B):", explanation: lines.join("\n"), default: "I") do |res|
|
54
|
+
res = res.upcase
|
55
|
+
case
|
56
|
+
when res.start_with?("I")
|
57
|
+
logger.info "Ignoring entry... #{entry.to_s(:identify) if entry}"
|
58
|
+
nil
|
59
|
+
when res.start_with?("A")
|
60
|
+
logger.info "All input entries with this same issue will be ignored for this launch"
|
61
|
+
@_skip_all_multiple_results = true
|
62
|
+
nil
|
63
|
+
when res.start_with?("C")
|
64
|
+
logger.info "Creating new person...#{"for entry #{entry.to_s(:identify)}" if entry}"
|
65
|
+
session.new_person
|
66
|
+
when res.start_with?("B")
|
67
|
+
raise error
|
68
|
+
when res && !res.empty? && (pos = res.to_i rescue nil) && (pos < error.candidates.length)
|
69
|
+
error.candidate(pos).tap do |person|
|
70
|
+
logger.info "Thanks!! You selected #{person.identify}"
|
71
|
+
sleep(1.5)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
if pos.is_a?(Numeric) && (pos >= error.candidates.length)
|
75
|
+
print "#{pos} is not a number in the range. "
|
76
|
+
else
|
77
|
+
print "#{res} is not an option. "
|
78
|
+
end
|
79
|
+
puts "Please select one of the offered options..."
|
80
|
+
sleep(1)
|
81
|
+
_with_each_prompt_to_select_user(error, increase_count: false, entry: entry)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
25
86
|
end
|
26
87
|
end
|
27
88
|
end
|
@@ -15,8 +15,10 @@ module Eco
|
|
15
15
|
def with_each_present(entries, people, options, log_starter: false)
|
16
16
|
found = []
|
17
17
|
micro.with_each(entries, people, options) do |entry, person|
|
18
|
-
if person.new?
|
19
|
-
|
18
|
+
if person.new?
|
19
|
+
if log_starter
|
20
|
+
session.logger.error("This person does not exist: #{entry.to_s(:identify)}")
|
21
|
+
end
|
20
22
|
next
|
21
23
|
end
|
22
24
|
found << person
|
@@ -15,8 +15,10 @@ module Eco
|
|
15
15
|
def with_each_starter(entries, people, options, log_present: false)
|
16
16
|
starters = []
|
17
17
|
micro.with_each(entries, people, options) do |entry, person|
|
18
|
-
if !person.new?
|
19
|
-
|
18
|
+
if !person.new?
|
19
|
+
if log_present
|
20
|
+
session.logger.error("This person (id: '#{person.id}') already exists: #{entry.to_s(:identify)}")
|
21
|
+
end
|
20
22
|
next
|
21
23
|
end
|
22
24
|
starters << person
|
data/lib/eco/api/organization.rb
CHANGED
@@ -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_similarity'
|
12
13
|
require_relative 'organization/person_schemas'
|
13
14
|
require_relative 'organization/policy_groups'
|
14
15
|
require_relative 'organization/login_providers'
|
@@ -2,6 +2,43 @@ module Eco
|
|
2
2
|
module API
|
3
3
|
module Organization
|
4
4
|
class People < Eco::Language::Models::Collection
|
5
|
+
# Error class that allows to handle cases where multiple people were found for the same criterion.
|
6
|
+
# @note its main purpose to prevent the creation of duplicates or override information between different people.
|
7
|
+
class MultipleSearchResults < StandardError
|
8
|
+
attr_reader :candidates, :property
|
9
|
+
# @param msg [String] the basic message error.
|
10
|
+
# @param candiates [Array<Person>] the people that match the same search criterion.
|
11
|
+
# @param property [String] the property of the person model that triggered the error (base of the search criterion).
|
12
|
+
def initialize(msg, candidates: [], property: "email")
|
13
|
+
@candidates = candidates
|
14
|
+
@property = property
|
15
|
+
super(msg + " " + candidates_summary)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param with_index [Boolean] to add an index to each candidate description.
|
19
|
+
# @return [Array<String>] the `candidates` identified
|
20
|
+
def identify_candidates(with_index: false)
|
21
|
+
candidates.map.each_with_index do |person, i|
|
22
|
+
index = with_index ? "#{i}. " : ""
|
23
|
+
msg = person.account ? (person.account_added? ? "(new user)" : "(user)") : "(no account)"
|
24
|
+
"#{index}#{msg} #{person.identify}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Person] the `candidate` in the `index` position
|
29
|
+
def candidate(index)
|
30
|
+
candidates[index]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def candidates_summary
|
36
|
+
lines = ["The following people have the same '#{property}':"]
|
37
|
+
lines.concat(identify_candidates(with_index: true)).join("\n ")
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
5
42
|
# build the shortcuts of Collection
|
6
43
|
attr_presence :account, :details
|
7
44
|
attr_collection :id, :external_id, :email, :name, :supervisor_id
|
@@ -78,34 +115,36 @@ module Eco
|
|
78
115
|
# @!group Searchers
|
79
116
|
|
80
117
|
# It searches a person using the parameters given.
|
118
|
+
# @note This is how the search function actually works:
|
119
|
+
# 1. if eP `id` is given, returns the person (if found), otherwise...
|
120
|
+
# 2. if `external_id` is given, returns the person (if found), otherwise...
|
121
|
+
# 3. if `strict` is `false` and `email` is given:
|
122
|
+
# - if there is only 1 person with that email, returns that person, otherwise...
|
123
|
+
# - if found but, there are many candidates, it raises MultipleSearchResults error
|
124
|
+
# - if person `external_id` matches `email`, returns that person
|
125
|
+
# @raise MultipleSearchResults if there are multiple people with the same `email`
|
126
|
+
# and there's no other criteria to find the person. It only gets to this point if
|
127
|
+
# `external_id` was **not** provided and we are **not** in 'strict' search mode.
|
128
|
+
# However, it could be we were in `strict` mode and `external_id` was not provided.
|
81
129
|
# @param id [String] the `internal id` of the person
|
82
130
|
# @param external_id [String] the `exernal_id` of the person
|
83
131
|
# @param email [String] the `email` of the person
|
84
|
-
# @param strict [Boolean] if should perform a
|
132
|
+
# @param strict [Boolean] if should perform a `:soft` or a `:strict` search. `strict` will avoid repeated email addresses.
|
85
133
|
# @return [Person, nil] the person we were searching, or `nil` if not found.
|
86
134
|
def person(id: nil, external_id: nil, email: nil, strict: false)
|
87
135
|
init_caches
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
if !pers && !strict && !email.to_s.strip.empty?
|
98
|
-
candidates = @by_non_users_email[email&.downcase.strip] || []
|
99
|
-
raise "Too many non-user candidates (#{candidates.length}) with email '#{email}'" if candidates.length > 1
|
100
|
-
pers = candidates.first
|
101
|
-
end
|
102
|
-
|
103
|
-
pers = @by_external_id[email&.downcase.strip]&.first if !pers && !email.to_s.strip.empty?
|
104
|
-
end
|
105
|
-
|
136
|
+
# normalize values
|
137
|
+
ext_id = !external_id.to_s.strip.empty? && external_id.strip
|
138
|
+
email = !email.to_s.strip.empty? && email.downcase.strip
|
139
|
+
|
140
|
+
pers = nil
|
141
|
+
pers ||= @by_id[id]&.first
|
142
|
+
pers ||= @by_external_id[ext_id]&.first
|
143
|
+
pers ||= person_by_email(email) unless strict && ext_id
|
106
144
|
pers
|
107
145
|
end
|
108
146
|
|
147
|
+
# @see Eco::API::Organization::People#person
|
109
148
|
def find(object, strict: false)
|
110
149
|
id = attr_value(object, "id")
|
111
150
|
external_id = attr_value(object, "external_id")
|
@@ -176,6 +215,12 @@ module Eco
|
|
176
215
|
end
|
177
216
|
# @!endgroup
|
178
217
|
|
218
|
+
# @!group Helper methods
|
219
|
+
def analytics
|
220
|
+
Eco::API::Organization::PeopleAnalytics.new(self.to_a)
|
221
|
+
end
|
222
|
+
# @!endgroup
|
223
|
+
|
179
224
|
protected
|
180
225
|
|
181
226
|
def on_change
|
@@ -184,15 +229,46 @@ module Eco
|
|
184
229
|
|
185
230
|
private
|
186
231
|
|
232
|
+
def person_by_email(email, prevent_duplicates: true)
|
233
|
+
return nil unless email
|
234
|
+
|
235
|
+
candidates = @by_non_users_email[email] || []
|
236
|
+
email_users = @by_users_email[email] || []
|
237
|
+
|
238
|
+
if pers = email_users.first
|
239
|
+
return pers if candidates.empty?
|
240
|
+
candidates = [pers] + candidates
|
241
|
+
elsif candidates.length == 1
|
242
|
+
return candidates.first
|
243
|
+
end
|
244
|
+
|
245
|
+
if prevent_duplicates && !candidates.empty?
|
246
|
+
msg = "Multiple search results match the criteria."
|
247
|
+
raise MultipleSearchResults.new(msg, candidates: candidates, property: "email")
|
248
|
+
end
|
249
|
+
|
250
|
+
@by_external_id[email]&.first
|
251
|
+
end
|
252
|
+
|
187
253
|
def init_caches
|
188
254
|
return if @caches_init
|
189
255
|
@by_id = to_h
|
190
|
-
@by_external_id = to_h('external_id')
|
191
|
-
@by_users_email =
|
192
|
-
@by_non_users_email = non_users.to_h('email')
|
256
|
+
@by_external_id = no_nil_key(to_h('external_id'))
|
257
|
+
@by_users_email = no_nil_key(existing_users.to_h('email'))
|
258
|
+
@by_non_users_email = no_nil_key(non_users.to_h('email'))
|
259
|
+
@by_email = no_nil_key(to_h('email'))
|
193
260
|
@caches_init = true
|
194
261
|
end
|
195
262
|
|
263
|
+
def existing_users
|
264
|
+
newFrom users.select {|u| !u.account_added?(:original)}
|
265
|
+
end
|
266
|
+
|
267
|
+
def no_nil_key(hash)
|
268
|
+
hash.tap {|h| h.delete(nil)}
|
269
|
+
end
|
270
|
+
|
271
|
+
|
196
272
|
end
|
197
273
|
end
|
198
274
|
end
|