eco-helpers 2.0.21 → 2.0.26

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -4
  3. data/eco-helpers.gemspec +0 -1
  4. data/lib/eco/api/common.rb +0 -1
  5. data/lib/eco/api/common/loaders.rb +2 -0
  6. data/lib/eco/api/common/loaders/base.rb +58 -0
  7. data/lib/eco/api/common/loaders/case_base.rb +33 -0
  8. data/lib/eco/api/common/loaders/error_handler.rb +2 -2
  9. data/lib/eco/api/common/loaders/parser.rb +30 -5
  10. data/lib/eco/api/common/loaders/policy.rb +1 -1
  11. data/lib/eco/api/common/loaders/use_case.rb +1 -1
  12. data/lib/eco/api/common/people/default_parsers.rb +1 -0
  13. data/lib/eco/api/common/people/default_parsers/csv_parser.rb +93 -1
  14. data/lib/eco/api/common/people/default_parsers/xls_parser.rb +53 -0
  15. data/lib/eco/api/common/people/entries.rb +83 -14
  16. data/lib/eco/api/common/people/entry_factory.rb +36 -21
  17. data/lib/eco/api/common/people/person_attribute_parser.rb +8 -0
  18. data/lib/eco/api/common/people/person_factory.rb +4 -2
  19. data/lib/eco/api/common/people/person_parser.rb +8 -2
  20. data/lib/eco/api/common/people/supervisor_helpers.rb +1 -1
  21. data/lib/eco/api/common/version_patches/ecoportal_api/external_person.rb +0 -8
  22. data/lib/eco/api/common/version_patches/ecoportal_api/internal_person.rb +0 -8
  23. data/lib/eco/api/microcases/set_core_with_supervisor.rb +4 -2
  24. data/lib/eco/api/microcases/set_supervisor.rb +29 -8
  25. data/lib/eco/api/microcases/with_each.rb +7 -3
  26. data/lib/eco/api/microcases/with_each_starter.rb +3 -2
  27. data/lib/eco/api/organization/people.rb +7 -1
  28. data/lib/eco/api/session.rb +18 -7
  29. data/lib/eco/api/session/batch.rb +1 -1
  30. data/lib/eco/api/session/batch/job.rb +42 -9
  31. data/lib/eco/api/usecases.rb +2 -2
  32. data/lib/eco/api/usecases/base_case.rb +2 -2
  33. data/lib/eco/api/usecases/base_io.rb +17 -4
  34. data/lib/eco/api/usecases/default_cases/create_case.rb +10 -1
  35. data/lib/eco/api/usecases/default_cases/create_details_case.rb +10 -1
  36. data/lib/eco/api/usecases/default_cases/create_details_with_supervisor_case.rb +10 -1
  37. data/lib/eco/api/usecases/default_cases/hris_case.rb +25 -1
  38. data/lib/eco/api/usecases/default_cases/upsert_case.rb +10 -1
  39. data/lib/eco/cli/config/default/input.rb +63 -10
  40. data/lib/eco/cli/config/default/options.rb +40 -8
  41. data/lib/eco/cli/config/default/usecases.rb +16 -0
  42. data/lib/eco/cli/config/default/workflow.rb +7 -4
  43. data/lib/eco/cli/config/filters.rb +6 -2
  44. data/lib/eco/cli/config/filters/input_filters.rb +3 -2
  45. data/lib/eco/cli/config/filters/people_filters.rb +3 -2
  46. data/lib/eco/cli/config/help.rb +1 -1
  47. data/lib/eco/cli/config/options_set.rb +6 -4
  48. data/lib/eco/cli/config/use_cases.rb +6 -3
  49. data/lib/eco/cli/scripting/args_helpers.rb +2 -2
  50. data/lib/eco/csv.rb +2 -0
  51. data/lib/eco/language/models/collection.rb +5 -2
  52. data/lib/eco/version.rb +1 -1
  53. metadata +4 -22
  54. data/lib/eco/api/common/base_loader.rb +0 -68
@@ -7,16 +7,20 @@ module Eco
7
7
  # @param entries [Eco::API::Common::People::Entries] the input entries with the data.
8
8
  # @param people [Eco::API::Organization::People] target existing _People_ of the current update.
9
9
  # @param options [Hash] the options.
10
+ # @param append_created [Boolean] whether or not a new person will be added to the `people` object.
10
11
  # @yield [entry, person] gives each entry, and the paired person thereof (new or existing).
11
12
  # @yieldparam entry [PersonEntry] the input entry with the data we should set on person.
12
13
  # @yieldparam person [Ecoportal::API::V1::Person] the found person that matches `entry`, or a new person otherwise.
13
14
  # @return [Eco::API::Organization::People] all the people, including new and existing ones.
14
- def with_each(entries, people, options)
15
+ def with_each(entries, people, options, append_created: true)
15
16
  @_skip_all_multiple_results = false
17
+ people_copy = people.newFrom(people.to_a)
16
18
  entries.each_with_object([]) do |entry, scoped|
17
19
  begin
18
- unless person = people.find(entry, strict: micro.strict_search?(options))
19
- person = session.new_person
20
+ unless person = people_copy.find(entry, strict: micro.strict_search?(options))
21
+ person = session.new_person.tap do |person|
22
+ people << person if append_created
23
+ end
20
24
  end
21
25
  rescue Eco::API::Organization::People::MultipleSearchResults => e
22
26
  unless @_skip_all_multiple_results
@@ -8,13 +8,14 @@ module Eco
8
8
  # @param people [Eco::API::Organization::People] target existing _People_ of the current update.
9
9
  # @param options [Hash] the options.
10
10
  # @param log_present [Boolean] log error message if an `entry` has match in `people`.
11
+ # @param append_created [Boolean] whether or not a new person will be added to the `people` object.
11
12
  # @yield [entry, person] gives each **new** `person` of `entries` that is not present in `people`.
12
13
  # @yieldparam entry [PersonEntry] the input entry with the data we should set on person.
13
14
  # @yieldparam person [Ecoportal::API::V1::Person] the **new** person.
14
15
  # @return [Eco::API::Organization::People] the starters.
15
- def with_each_starter(entries, people, options, log_present: false)
16
+ def with_each_starter(entries, people, options, log_present: false, append_created: true)
16
17
  starters = []
17
- micro.with_each(entries, people, options) do |entry, person|
18
+ micro.with_each(entries, people, options, append_created: append_created) do |entry, person|
18
19
  if !person.new?
19
20
  if log_present
20
21
  session.logger.error("This person (id: '#{person.id}') already exists: #{entry.to_s(:identify)}")
@@ -210,6 +210,12 @@ module Eco
210
210
  to_h(:supervisor_id)
211
211
  end
212
212
 
213
+ def group_by_schema
214
+ to_h do |person|
215
+ person.details && person.details.schema_id
216
+ end
217
+ end
218
+
213
219
  def to_h(attr = "id")
214
220
  super(attr || "id")
215
221
  end
@@ -252,7 +258,7 @@ module Eco
252
258
 
253
259
  def init_caches
254
260
  return if @caches_init
255
- @by_id = to_h
261
+ @by_id = no_nil_key(to_h)
256
262
  @by_external_id = no_nil_key(to_h('external_id'))
257
263
  @by_users_email = no_nil_key(existing_users.to_h('email'))
258
264
  @by_non_users_email = no_nil_key(non_users.to_h('email'))
@@ -66,6 +66,16 @@ module Eco
66
66
  @presets_factory ||= Eco::API::Organization::PresetsFactory.new(enviro: enviro)
67
67
  end
68
68
 
69
+ # @return [Eco::Data::Mapper] the mappings between the internal and external attribute/property names.
70
+ def fields_mapper
71
+ return @fields_mapper if instance_variable_defined?(:@fields_mapper)
72
+ mappings = []
73
+ if map_file = config.people.fields_mapper
74
+ mappings = map_file ? file_manager.load_json(map_file) : []
75
+ end
76
+ @fields_mapper = Eco::Data::Mapper.new(mappings)
77
+ end
78
+
69
79
  # Helper to obtain a EntryFactory
70
80
  # @param schema [String, Ecoportal::API::V1::PersonSchema] `schema` to which associate the EntryFactory,
71
81
  # where `String` can be the _name_ or the _id_ of the schema.
@@ -79,15 +89,11 @@ module Eco
79
89
  return @entry_factories[schema&.id]
80
90
  end
81
91
 
82
- mappings = []
83
- if map_file = config.people.fields_mapper
84
- mappings = map_file ? file_manager.load_json(map_file) : []
85
- end
86
92
  @entry_factories[schema&.id] = Eco::API::Common::People::EntryFactory.new(
87
93
  enviro,
88
94
  schema: schema,
89
95
  person_parser: config.people.parser,
90
- attr_map: Eco::Data::Mapper.new(mappings)
96
+ attr_map: fields_mapper
91
97
  )
92
98
  end
93
99
 
@@ -147,9 +153,14 @@ module Eco
147
153
  # Generates an entries collection from a csv input file.
148
154
  # @see Eco::API::Common::People::EntryFactory#entries
149
155
  # @param file [String] file to generate the entries from.
156
+ # @param (see Eco::API::Session#entries)
150
157
  # @return [Eco::API::Common::People::Entries] collection of entries.
151
- def csv_entries(file)
152
- return entries(file: file, format: :csv)
158
+ def csv_entries(file, **kargs)
159
+ kargs.merge!({
160
+ file: file,
161
+ format: :csv
162
+ })
163
+ return entries(**kargs)
153
164
  end
154
165
 
155
166
  # Generates the collection of entries that should be discarded from an update.
@@ -137,7 +137,7 @@ module Eco
137
137
  rescue error_type => e
138
138
  raise unless retries_left > 0
139
139
  explanation = "Batch TimeOut. You have #{retries_left} retries left."
140
- prompt_user("Do you want to retry (y/N)?", explanation, default: "Y", timeout: 10) do |response|
140
+ prompt_user(" Do you want to retry (y/N)?", default: "Y", explanation: explanation, timeout: 10) do |response|
141
141
  if response.upcase.start_with?("Y")
142
142
  offer_retry_on(error_type, retries_left - 1, &block)
143
143
  else
@@ -164,12 +164,17 @@ module Eco
164
164
  # @return [Eco::API::Session::Batch::Status]
165
165
  def launch(simulate: false)
166
166
  pqueue = processed_queue
167
- @requests = pqueue.map {|e| as_update(e)}
167
+ @requests = as_update(pqueue)
168
168
  pre_checks(requests, simulate: simulate)
169
169
 
170
- unless simulate
170
+ if simulate
171
+ if options.dig(:requests, :backup)
172
+ req_backup = as_update(pqueue, add_feedback: false)
173
+ backup_update(req_backup, simulate: simulate)
174
+ end
175
+ else
171
176
  if pqueue.length > 0
172
- req_backup = pqueue.map {|e| as_update(e, add_feedback: false)}
177
+ req_backup = as_update(pqueue, add_feedback: false)
173
178
  backup_update(req_backup)
174
179
  session.batch.launch(pqueue, method: type).tap do |job_status|
175
180
  @status = job_status
@@ -220,13 +225,26 @@ module Eco
220
225
  end.join("\n")
221
226
  end
222
227
 
223
- def as_update(*args)
224
- feedback.as_update(*args)
228
+ def as_update(data, *args)
229
+ if data.is_a?(Array)
230
+ data.map do |e|
231
+ feedback.as_update(e, *args)
232
+ end.compact.select {|e| e && !e.empty?}
233
+ else
234
+ feedback.as_update(data, *args)
235
+ end
225
236
  end
226
237
 
227
238
  def processed_queue
228
239
  @queue.each {|e| @callbacks[e].call(e) if @callbacks.key?(e) }
229
- apply_policies(api_included(@queue)).select {|e| !as_update(e).empty?}
240
+ apply_policies(api_included(@queue)).select do |e|
241
+ !as_update(e).empty?
242
+ end.select do |e|
243
+ next true unless e.is_a?(Ecoportal::API::V1::Person)
244
+ next true unless e.new?
245
+ # new people should either have account or details
246
+ e.account || e.details
247
+ end
230
248
  end
231
249
 
232
250
  # if there is a config definition to exclude entries
@@ -235,7 +253,13 @@ module Eco
235
253
  def api_included(full_queue)
236
254
  return full_queue if type == :create
237
255
  return full_queue unless excluded = session.config.people.api_excluded
238
- full_queue.select {|entry| !excluded.call(entry, session, options, self)}
256
+ if options.dig(:include, :excluded, :only)
257
+ full_queue.select {|entry| excluded.call(entry, session, options, self)}
258
+ elsif options.dig(:include, :excluded)
259
+ full_queue
260
+ else
261
+ full_queue.select {|entry| !excluded.call(entry, session, options, self)}
262
+ end
239
263
  end
240
264
 
241
265
  # Applies the changes introduced by api policies
@@ -280,6 +304,9 @@ module Eco
280
304
  if !simulate && status
281
305
  status.queue.map do |entry|
282
306
  if status.success?(entry)
307
+ if type == :create && entry.respond_to?(:id=)
308
+ entry.id = status[entry].body["id"]
309
+ end
283
310
  entry.consolidate! if entry.respond_to?(:consolidate!)
284
311
  #else # do not entry.reset! (keep track on changes still)
285
312
  end
@@ -300,16 +327,22 @@ module Eco
300
327
  end
301
328
  end
302
329
  elsif simulate
330
+ fake_id = 111111111111111111111111
303
331
  queue.map do |entry|
332
+ if type == :create && entry.respond_to?(:id=)
333
+ entry.id = fake_id.to_s
334
+ fake_id += 1
335
+ end
304
336
  entry.consolidate! if entry.respond_to?(:consolidate!)
305
337
  end
306
338
  end
307
339
  end
308
340
 
309
341
  # Keep a copy of the requests for future reference
310
- def backup_update(requests)
342
+ def backup_update(requests, simulate: false)
343
+ dry_run = simulate ? "_dry_run" : ""
311
344
  dir = config.people.requests_folder
312
- file = File.join(dir, "#{type}_data.json")
345
+ file = File.join(dir, "#{type}_data#{dry_run}.json")
313
346
  file_manager.save_json(requests, file, :timestamp)
314
347
  end
315
348
 
@@ -2,7 +2,7 @@ module Eco
2
2
  module API
3
3
  class UseCases
4
4
 
5
- class UnkownCase < Exception
5
+ class UnkownCase < StandardError
6
6
  def initialize(msg = nil, case_name: nil, type: nil)
7
7
  msg ||= "Unkown case"
8
8
  msg += ". Case name '#{case_name}'" if case_name
@@ -11,7 +11,7 @@ module Eco
11
11
  end
12
12
  end
13
13
 
14
- class AmbiguousCaseReference < Exception
14
+ class AmbiguousCaseReference < StandardError
15
15
  def initialize(msg = nil, case_name: nil)
16
16
  msg ||= "You must specify type when there are multiple cases with same name"
17
17
  msg += ". Case name '#{case_name}'" if case_name
@@ -4,7 +4,7 @@ module Eco
4
4
  # Core class of UseCases. It basically defines and manages allowed `types`
5
5
  class BaseCase
6
6
 
7
- class InvalidType < Exception
7
+ class InvalidType < StandardError
8
8
  def initialize(msg = nil, type:, types:)
9
9
  msg ||= "Invalid type."
10
10
  msg = "Given type '#{type}'. Valid types: #{types}"
@@ -13,7 +13,7 @@ module Eco
13
13
  end
14
14
 
15
15
  extend Eco::API::Common::ClassHelpers
16
-
16
+
17
17
  @types = [:import, :filter, :transform, :sync, :error_handler, :export, :other]
18
18
 
19
19
  class << self
@@ -5,6 +5,19 @@ module Eco
5
5
  class BaseIO < BaseCase
6
6
  @types = BaseCase.types
7
7
 
8
+ class MissingParameter < StandardError
9
+ attr_reader :type, :required, :given
10
+
11
+ def initialize(msg = nil, type: nil, required:, given:)
12
+ @type = type
13
+ @required = required
14
+ @given = given
15
+ msg += " of type '#{type}'" if type
16
+ msg += " requires an object '#{required}'. Given: #{given}."
17
+ super(msg)
18
+ end
19
+ end
20
+
8
21
  class << self
9
22
  def input_required?(type)
10
23
  !valid_type?(type) || [:import, :sync].include?(type)
@@ -80,13 +93,13 @@ module Eco
80
93
  def validate_args(input:, people:, session:, options:)
81
94
  case
82
95
  when !session.is_a?(Eco::API::Session)
83
- raise "A UseCase needs a Session object. Given: #{session}"
96
+ raise MissingParameter.new("UseCase", required: :session, given: session.class)
84
97
  when input_required? && !input
85
- raise "UseCase of type '#{type}' requires a valid input. None given"
98
+ raise MissingParameter.new("UseCase", type: type, required: :input, given: input.class)
86
99
  when people_required? && !people.is_a?(Eco::API::Organization::People)
87
- raise "UseCase of type '#{type}' requires a People object. Given: #{people}"
100
+ raise MissingParameter.new("UseCase", type: type, required: :people, given: people.class)
88
101
  when !options || (options && !options.is_a?(Hash))
89
- raise "To inject dependencies via ':options' it should be a Hash object. Given: #{options}"
102
+ raise MissingParameter.new("Use Case options", required: :Hash, given: options.class)
90
103
  end
91
104
  true
92
105
  end
@@ -2,12 +2,15 @@ class Eco::API::UseCases::DefaultCases::CreateCase < Eco::API::Common::Loaders::
2
2
  name "create"
3
3
  type :sync
4
4
 
5
+ attr_reader :options
6
+
5
7
  def main(entries, people, session, options, usecase)
8
+ options = @options
6
9
  micro = session.micro
7
10
  creation = session.new_job("main", "create", :create, usecase)
8
11
  supers = session.new_job("post", "supers", :update, usecase, :core)
9
12
 
10
- micro.with_each_starter(entries, people, options, log_present: true) do |entry, person|
13
+ micro.with_each_starter(entries, people, options, log_present: true, append_created: append_created) do |entry, person|
11
14
  creation.add(person)
12
15
  micro.set_core_with_supervisor(entry, person, people, supers, options)
13
16
  entry.set_details(person) unless options.dig(:exclude, :details)
@@ -15,4 +18,10 @@ class Eco::API::UseCases::DefaultCases::CreateCase < Eco::API::Common::Loaders::
15
18
  end
16
19
  end
17
20
 
21
+ private
22
+
23
+ def append_created
24
+ options.dig(:people, :append_created)
25
+ end
26
+
18
27
  end
@@ -2,15 +2,24 @@ class Eco::API::UseCases::DefaultCases::CreateDetailsCase < Eco::API::Common::Lo
2
2
  name "create-details"
3
3
  type :sync
4
4
 
5
+ attr_reader :options
6
+
5
7
  def main(entries, people, session, options, usecase)
8
+ @options = options
6
9
  micro = session.micro
7
10
  creation = session.new_job("main", "create", :create, usecase)
8
11
 
9
- micro.with_each_starter(entries, people, options, log_present: true) do |entry, person|
12
+ micro.with_each_starter(entries, people, options, log_present: true, append_created: append_created) do |entry, person|
10
13
  creation.add(person)
11
14
  micro.set_core(entry, person, options)
12
15
  entry.set_details(person) unless options.dig(:exclude, :details)
13
16
  end
14
17
  end
15
18
 
19
+ private
20
+
21
+ def append_created
22
+ options.dig(:people, :append_created)
23
+ end
24
+
16
25
  end
@@ -2,16 +2,25 @@ class Eco::API::UseCases::DefaultCases::CreateDetailsWithSupervisorCase < Eco::A
2
2
  name "create-details-with-supervisor"
3
3
  type :sync
4
4
 
5
+ attr_reader :options
6
+
5
7
  def main(entries, people, session, options, usecase)
8
+ @options = options
6
9
  micro = session.micro
7
10
  creation = session.new_job("main", "create", :create, usecase)
8
11
  supers = session.new_job("post", "supers", :update, usecase, :core)
9
12
 
10
- micro.with_each_starter(entries, people, options, log_present: true) do |entry, person|
13
+ micro.with_each_starter(entries, people, options, log_present: true, append_created: append_created) do |entry, person|
11
14
  creation.add(person)
12
15
  micro.set_core_with_supervisor(entry, person, people, supers, options)
13
16
  entry.set_details(person) unless options.dig(:exclude, :details)
14
17
  end
15
18
  end
16
19
 
20
+ private
21
+
22
+ def append_created
23
+ options.dig(:people, :append_created)
24
+ end
25
+
17
26
  end
@@ -3,8 +3,11 @@ class Eco::API::UseCases::DefaultCases::HrisCase < Eco::API::Common::Loaders::Us
3
3
  type :sync
4
4
 
5
5
  attr_reader :creation, :update, :supers, :leavers
6
+ attr_reader :people, :session, :options
6
7
 
7
8
  def main(entries, people, session, options, usecase)
9
+ @session = session; @options = options; @people = people
10
+ require_only_one_schema!
8
11
  micro = session.micro
9
12
  @creation = session.new_job("main", "create", :create, usecase)
10
13
  @update = session.new_job("main", "update", :update, usecase)
@@ -15,7 +18,7 @@ class Eco::API::UseCases::DefaultCases::HrisCase < Eco::API::Common::Loaders::Us
15
18
  leavers.add(person, &method(:leavers_callback))
16
19
  end
17
20
 
18
- micro.with_each(entries, people, options) do |entry, person|
21
+ micro.with_each(entries, people, options, append_created: append_created) do |entry, person|
19
22
  person.new? ? creation.add(person) : update.add(person)
20
23
  micro.set_core_with_supervisor(entry, person, people, supers, options)
21
24
  entry.set_details(person) unless options.dig(:exclude, :details)
@@ -25,9 +28,30 @@ class Eco::API::UseCases::DefaultCases::HrisCase < Eco::API::Common::Loaders::Us
25
28
 
26
29
  private
27
30
 
31
+ def append_created
32
+ options.dig(:people, :append_created)
33
+ end
34
+
28
35
  def leavers_callback(person)
29
36
  person.supervisor_id = nil
30
37
  person.account = nil if person.account
31
38
  end
32
39
 
40
+ def require_only_one_schema!
41
+ unless schema_id = options.dig(:people, :filter, :details, :schema_id)
42
+ active_schema = session.schema
43
+ other_schemas = session.schemas.map(&:id) - [active_schema.id]
44
+ other_people = people.group_by_schema.values_at(*other_schemas).map(&:to_a).flatten
45
+ if other_people.length > 3
46
+ msg = "There are #{other_people.length} people in schemas other than #{active_schema.name}."
47
+ msg << " Please, use the filter option '-schema_id SchemaName' for the 'hris' case to only include those of that schema"
48
+ msg << " in the current update. The HRIS case identifies people that are not in the file as leavers"
49
+ msg << " (as it will remove the account of all the people of other schemas if they are not in the input file)."
50
+ msg << "\n For example: -schema-id '#{active_schema.name.downcase}'"
51
+ logger.error(msg)
52
+ raise msg
53
+ end
54
+ end
55
+ end
56
+
33
57
  end
@@ -2,13 +2,16 @@ class Eco::API::UseCases::DefaultCases::UpsertCase < Eco::API::Common::Loaders::
2
2
  name "upsert"
3
3
  type :sync
4
4
 
5
+ attr_reader :options
6
+
5
7
  def main(entries, people, session, options, usecase)
8
+ @options = options
6
9
  micro = session.micro
7
10
  creation = session.new_job("main", "create", :create, usecase)
8
11
  update = session.new_job("main", "update", :update, usecase)
9
12
  supers = session.new_job("post", "supers", :update, usecase, :core)
10
13
 
11
- micro.with_each(entries, people, options) do |entry, person|
14
+ micro.with_each(entries, people, options, append_created: append_created) do |entry, person|
12
15
  person.new? ? creation.add(person) : update.add(person)
13
16
  micro.set_core_with_supervisor(entry, person, people, supers, options)
14
17
  entry.set_details(person) unless options.dig(:exclude, :details)
@@ -16,4 +19,10 @@ class Eco::API::UseCases::DefaultCases::UpsertCase < Eco::API::Common::Loaders::
16
19
  end
17
20
  end
18
21
 
22
+ private
23
+
24
+ def append_created
25
+ options.dig(:people, :append_created)
26
+ end
27
+
19
28
  end