eco-helpers 2.0.18 → 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 +14 -1
- data/lib/eco/api/common/people/entry_factory.rb +26 -9
- data/lib/eco/api/common/people/person_entry.rb +1 -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/helpers.rb +30 -0
- data/lib/eco/api/common/session/helpers/prompt_user.rb +34 -0
- 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/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 -1
- data/lib/eco/api/organization/people.rb +92 -23
- 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/session.rb +5 -2
- data/lib/eco/api/session/batch.rb +7 -5
- data/lib/eco/api/usecases/default_cases/analyse_people_case.rb +12 -35
- data/lib/eco/api/usecases/default_cases/to_csv_case.rb +81 -36
- data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +3 -4
- data/lib/eco/api/usecases/ooze_samples/ooze_update_case.rb +3 -2
- data/lib/eco/cli/config/default/options.rb +2 -1
- data/lib/eco/cli/config/default/usecases.rb +2 -0
- data/lib/eco/cli/config/default/workflow.rb +4 -1
- data/lib/eco/csv.rb +4 -2
- data/lib/eco/data/fuzzy_match.rb +63 -21
- data/lib/eco/data/fuzzy_match/ngrams_score.rb +7 -2
- data/lib/eco/data/fuzzy_match/pairing.rb +0 -1
- data/lib/eco/data/fuzzy_match/result.rb +7 -1
- data/lib/eco/data/fuzzy_match/results.rb +12 -6
- data/lib/eco/version.rb +1 -1
- metadata +4 -2
- data/lib/eco/api/organization/people_analytics.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14260868c76936513a93d4d104eacebbd11e47ed05806d4102ee76196a300d2b
|
4
|
+
data.tar.gz: 35784d03a18f89d2ce8bf5c4105e0eaa647dd10b4e1fee03897319d9ad838760
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 514d71e93bfa4fb854d9062be03306e154a4dfd184256ab03da30e2bf4bb2a45fb305efd5e2206821e522dc7cc0bfbc69e75285e3c6292dca78f359bd166f52a
|
7
|
+
data.tar.gz: c99a424905916cef61333c18bb31726e90fb9759a4a1b747b72ab123f4bc09ecc6962205a966b3ea46aa1f0a4cc3dd7f72a8c53829cc764a7993d64ad45495bf
|
data/CHANGELOG.md
CHANGED
@@ -1,13 +1,26 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## [2.0.19] - 2021-05-xx
|
5
|
+
|
6
|
+
### Added
|
7
|
+
- Better error message for people searches & **offer** to select among the candidates:
|
8
|
+
- `Eco::API::Organization::People::MultipleSearchResults`, triggered from `Eco::API::Organization::People#find`
|
9
|
+
- `Eco::API::MicroCases#with_each` will offer the selection of candidates
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
- **renamed** and repurposed `Eco::API::Organization::PeopleAnalytics` to `PeopleSimilarity`
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
- `Eco::Data::FuzzyMatch` adjustments for configuration propagation + some fixes
|
16
|
+
- Command option `-entries-from` can still be useful when used to obtain `-get-partial` of people base for `:export` use cases !!
|
17
|
+
|
4
18
|
## [2.0.18] - 2021-05-25
|
5
19
|
|
6
20
|
### Added
|
7
21
|
- **`-one-off`** option to not having to type the `-api-key` every time you launch one-off scripts
|
8
22
|
- `-api-key` will store the key to the `./.env_one_off` file (supports update and multi-environment)
|
9
23
|
|
10
|
-
### Changed
|
11
24
|
### Fixed
|
12
25
|
- patched `Exception#patch_full_message` to do not enter into a cyclic error rescue
|
13
26
|
- also rescue on `workflow.rescue`
|
@@ -2,6 +2,10 @@ module Eco
|
|
2
2
|
module API
|
3
3
|
module Common
|
4
4
|
module People
|
5
|
+
# TODO: EntryFactory should suppport multiple schemas itself
|
6
|
+
# => currently, it's through session.entry_factory(schema: id), but this is wrong
|
7
|
+
# => This way, Entries and PersonEntry will be able to refer to attr_map and person_parser linked to schema_id
|
8
|
+
# => "schema_id" should be an optional column in the input file, or parsable via a custom parser to scope the schema
|
5
9
|
# Helper factory class to generate entries (input entries).
|
6
10
|
# @attr_reader schema [Ecoportal::API::V1::PersonSchema] person schema to be used in this entry factory
|
7
11
|
class EntryFactory < Eco::API::Common::Session::BaseSession
|
@@ -12,7 +16,7 @@ module Eco
|
|
12
16
|
# @param schema [Ecoportal::API::V1::PersonSchema] schema of person details that the parser will be based upon.
|
13
17
|
# @param person_parser [nil, Eco::API::Common::People::PersonParser] set of attribute, type and format parsers/serializers.
|
14
18
|
# @param attr_map [nil, Eco::Data::Mapper] attribute names mapper to translate external names into internal ones and _vice versa_.
|
15
|
-
def initialize(e, schema:, person_parser: nil, attr_map: nil)
|
19
|
+
def initialize(e, schema:, person_parser: nil, default_parser: nil, attr_map: nil)
|
16
20
|
fatal "Constructor needs a PersonSchema. Given: #{schema}" if !schema.is_a?(Ecoportal::API::V1::PersonSchema)
|
17
21
|
fatal "Expecting PersonParser. Given: #{person_parser}" if person_parser && !person_parser.is_a?(Eco::API::Common::People::PersonParser)
|
18
22
|
fatal "Expecting Mapper object. Given: #{fields_mapper}" if attr_map && !attr_map.is_a?(Eco::Data::Mapper)
|
@@ -22,14 +26,25 @@ module Eco
|
|
22
26
|
@source_person_parser = person_parser
|
23
27
|
|
24
28
|
# load default parser + custom parsers
|
25
|
-
|
29
|
+
@default_parser = default_parser&.new(schema: @schema) || Eco::API::Common::People::DefaultParsers.new(schema: @schema)
|
30
|
+
base_parser = @default_parser.merge(@source_person_parser)
|
26
31
|
# new parser with linked schema
|
27
32
|
@person_parser = @source_person_parser.new(schema: @schema).merge(base_parser)
|
28
33
|
@person_parser_patch_version = @source_person_parser.patch_version
|
29
|
-
|
30
34
|
@attr_map = attr_map
|
31
35
|
end
|
32
36
|
|
37
|
+
def newFactory(schema: nil)
|
38
|
+
self.class.new(
|
39
|
+
environment,
|
40
|
+
schema: schema,
|
41
|
+
person_parser: @source_person_parser,
|
42
|
+
default_parser: @default_parser,
|
43
|
+
attr_map: @attr_map
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
|
33
48
|
# provides with a Eco::API::Common::People::PersonParser object (collection of attribute parsers)
|
34
49
|
# @note if the custom person parser has changed, it updates the copy of this EntryFactory instance
|
35
50
|
# @return [Eco::API::Common::People::PersonParser] set of attribute, type and format parsers/serializers.
|
@@ -43,7 +58,7 @@ module Eco
|
|
43
58
|
|
44
59
|
# key method to generate objects of `PersonEntry` that share dependencies via this `EntryFactory` environment.
|
45
60
|
# @note this method is necessary to make the factory object work as a if it was a class `PersonEntry` you can call `new` on.
|
46
|
-
# @param data [
|
61
|
+
# @param data [Hash, Person] data to be parsed/serialized. Parsed: the external hashed entry. Serialized: a Person object.
|
47
62
|
# @return [Eco::API::Common::People::PersonEntry]
|
48
63
|
def new(data, dependencies: {})
|
49
64
|
PersonEntry.new(
|
@@ -104,7 +119,7 @@ module Eco
|
|
104
119
|
# @param format [Symbol] it specifies the format of the output `file:` (i.e. `:xml`, `:csv`). There must be a parser/serializer defined for it.
|
105
120
|
# @param encoding [String] optional parameter to geneate `file:` content by unsing certain encoding.
|
106
121
|
# @return [Void].
|
107
|
-
def export(data:, file: "export", format: :csv, encoding: "utf-8")
|
122
|
+
def export(data:, file: "export", format: :csv, encoding: "utf-8", internal_names: false)
|
108
123
|
fatal("data: Expected Eco::API::Organization::People object. Given: #{data.class}") unless data.is_a?(Eco::API::Organization::People)
|
109
124
|
fatal("A file should be specified.") unless !file.to_s.strip.empty?
|
110
125
|
fatal("Format should be a Symbol. Given '#{format}'") if format && !format.is_a?(Symbol)
|
@@ -112,16 +127,18 @@ module Eco
|
|
112
127
|
|
113
128
|
run = true
|
114
129
|
if Eco::API::Common::Session::FileManager.file_exists?(file)
|
115
|
-
|
116
|
-
|
117
|
-
|
130
|
+
prompt_user("The file '#{file}' already exists. Do you want to overwrite it? (Y/n):", default: "Y") do |response|
|
131
|
+
run = (response == "") || reponse.upcase.start_with?("Y")
|
132
|
+
end
|
118
133
|
end
|
119
134
|
|
120
135
|
if run
|
121
136
|
deps = {"supervisor_id" => {people: data}}
|
122
137
|
|
123
138
|
data_entries = data.map do |person|
|
124
|
-
self.new(person, dependencies: deps).
|
139
|
+
self.new(person, dependencies: deps).yield_self do |entry|
|
140
|
+
internal_names ? entry.mapped_entry : entry.external_entry
|
141
|
+
end
|
125
142
|
end
|
126
143
|
|
127
144
|
File.open(file, "w", enconding: encoding) do |fd|
|
@@ -216,6 +216,7 @@ module Eco
|
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
219
|
+
# TO DO: use person.details.schema_id to switch @emap and @person_parser (or just crash if they don't match?)
|
219
220
|
# Setter to fill in all the schema `details` fields of the `Person` that are present in the `Entry`.
|
220
221
|
# @note it only sets those details properties defined in the entry.
|
221
222
|
# Meaning that if an details property is not present in the entry, this will not be set on the target person.
|
@@ -11,6 +11,8 @@ module Eco
|
|
11
11
|
attr_reader :api, :file_manager, :logger
|
12
12
|
alias_method :fm, :file_manager
|
13
13
|
|
14
|
+
include Session::Helpers
|
15
|
+
|
14
16
|
def initialize(e)
|
15
17
|
raise "Expected object Eco::API::Common::Session::Environment. Given: #{e.class}" unless e.is_a?(Environment)
|
16
18
|
self.environment = e
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'helpers/prompt_user'
|
2
|
+
|
3
|
+
module Eco
|
4
|
+
module API
|
5
|
+
module Common
|
6
|
+
module Session
|
7
|
+
module Helpers
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def included(base)
|
11
|
+
base.send(:include, InstanceMethods)
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
include Helpers::PromptUser
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -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
|
@@ -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,7 +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/
|
12
|
+
require_relative 'organization/people_similarity'
|
13
13
|
require_relative 'organization/person_schemas'
|
14
14
|
require_relative 'organization/policy_groups'
|
15
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")
|
@@ -190,16 +229,46 @@ module Eco
|
|
190
229
|
|
191
230
|
private
|
192
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
|
+
|
193
253
|
def init_caches
|
194
254
|
return if @caches_init
|
195
255
|
@by_id = to_h
|
196
|
-
@by_external_id = to_h('external_id')
|
197
|
-
@by_users_email =
|
198
|
-
@by_non_users_email = non_users.to_h('email')
|
199
|
-
@by_email = 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'))
|
200
260
|
@caches_init = true
|
201
261
|
end
|
202
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
|
+
|
203
272
|
end
|
204
273
|
end
|
205
274
|
end
|