eco-helpers 2.0.18 → 2.0.19
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.
- 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
|