eco-helpers 0.6.17 → 0.7.1

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +19 -0
  3. data/.yardopts +2 -2
  4. data/Gemfile +6 -0
  5. data/Rakefile +27 -0
  6. data/eco-helpers.gemspec +9 -6
  7. data/lib/eco/api.rb +2 -1
  8. data/lib/eco/api/common/people.rb +1 -1
  9. data/lib/eco/api/common/people/base_parser.rb +31 -1
  10. data/lib/eco/api/common/people/default_parsers.rb +5 -1
  11. data/lib/eco/api/common/people/default_parsers/csv_parser.rb +37 -0
  12. data/lib/eco/api/common/people/default_parsers/numeric_parser.rb +0 -1
  13. data/lib/eco/api/common/people/entries.rb +14 -18
  14. data/lib/eco/api/common/people/entry_factory.rb +97 -9
  15. data/lib/eco/api/common/people/person_entry.rb +147 -206
  16. data/lib/eco/api/common/people/person_entry_attribute_mapper.rb +212 -0
  17. data/lib/eco/api/common/people/person_factory.rb +10 -12
  18. data/lib/eco/api/common/people/person_parser.rb +97 -37
  19. data/lib/eco/api/common/session/base_session.rb +1 -2
  20. data/lib/eco/api/common/session/file_manager.rb +1 -1
  21. data/lib/eco/api/organization.rb +2 -1
  22. data/lib/eco/api/organization/people.rb +54 -22
  23. data/lib/eco/api/organization/person_schemas.rb +54 -0
  24. data/lib/eco/api/organization/policy_groups.rb +5 -9
  25. data/lib/eco/api/organization/{presets.rb → presets_factory.rb} +1 -1
  26. data/lib/eco/api/policies.rb +10 -0
  27. data/lib/eco/api/policies/base_policy.rb +14 -0
  28. data/lib/eco/api/policies/policy.rb +20 -0
  29. data/lib/eco/api/policies/used_policies.rb +37 -0
  30. data/lib/eco/api/session.rb +36 -34
  31. data/lib/eco/api/session/batch.rb +94 -44
  32. data/lib/eco/api/session/batch_job.rb +108 -48
  33. data/lib/eco/api/session/batch_jobs.rb +4 -5
  34. data/lib/eco/api/session/batch_status.rb +70 -11
  35. data/lib/eco/api/session/config.rb +22 -5
  36. data/lib/eco/api/session/config/files.rb +10 -1
  37. data/lib/eco/api/session/config/people.rb +18 -5
  38. data/lib/eco/api/session/config/policies.rb +29 -0
  39. data/lib/eco/api/session/config/use_cases.rb +3 -7
  40. data/lib/eco/api/session/job_groups.rb +9 -10
  41. data/lib/eco/api/usecases.rb +2 -1
  42. data/lib/eco/api/usecases/base_case.rb +7 -2
  43. data/lib/eco/api/usecases/default_cases/change_email_case.rb +4 -2
  44. data/lib/eco/api/usecases/default_cases/create_case.rb +2 -1
  45. data/lib/eco/api/usecases/default_cases/create_details_case.rb +3 -1
  46. data/lib/eco/api/usecases/default_cases/create_details_with_supervisor_case.rb +4 -2
  47. data/lib/eco/api/usecases/default_cases/hris_case.rb +20 -13
  48. data/lib/eco/api/usecases/default_cases/new_email_case.rb +3 -1
  49. data/lib/eco/api/usecases/default_cases/new_id_case.rb +4 -2
  50. data/lib/eco/api/usecases/default_cases/recover_db_case.rb +9 -5
  51. data/lib/eco/api/usecases/default_cases/remove_account_case.rb +4 -2
  52. data/lib/eco/api/usecases/default_cases/set_supervisor_case.rb +4 -2
  53. data/lib/eco/api/usecases/default_cases/to_csv_case.rb +2 -2
  54. data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +2 -2
  55. data/lib/eco/api/usecases/default_cases/update_case.rb +16 -2
  56. data/lib/eco/api/usecases/default_cases/update_details_case.rb +3 -1
  57. data/lib/eco/api/usecases/default_cases/upsert_case.rb +25 -3
  58. data/lib/eco/api/usecases/use_case.rb +23 -140
  59. data/lib/eco/api/usecases/use_case_chain.rb +95 -0
  60. data/lib/eco/api/usecases/use_case_io.rb +117 -0
  61. data/lib/eco/api/usecases/use_group.rb +25 -5
  62. data/lib/eco/common/base_cli_backup.rb +1 -0
  63. data/lib/eco/language/models.rb +1 -1
  64. data/lib/eco/language/models/collection.rb +42 -31
  65. data/lib/eco/language/models/parser_serializer.rb +68 -0
  66. data/lib/eco/version.rb +1 -1
  67. metadata +93 -38
  68. data/lib/eco/api/common/people/types.rb +0 -47
  69. data/lib/eco/api/usecases/case_data.rb +0 -13
  70. data/lib/eco/language/models/attribute_parser.rb +0 -38
  71. data/lib/eco/lexic/dictionary.rb +0 -33
  72. data/lib/eco/lexic/dictionary/dictionary.txt +0 -355484
  73. data/lib/eco/lexic/dictionary/tags.json +0 -38
@@ -1,27 +1,78 @@
1
1
  module Eco
2
2
  module API
3
3
  class Session
4
- # important! the handler should probably only create logs and save the update with same timestamp
5
4
  class Batch < Common::Session::BaseSession
6
5
 
7
6
  DEFAULT_BATCH_BLOCK = 100
8
- VALID_METHODS = ['get', 'create', 'update', 'upsert', 'delete']
7
+ VALID_METHODS = [:get, :create, :update, :upsert, :delete]
9
8
 
10
- # people can be: empty, an api object, a people object, or an array with id's or persons
11
- def get_people(people = nil, api: nil, params: {per_page: DEFAULT_BATCH_BLOCK})
12
- api = api || self.api
13
- return batch_from(people, 'get', api, params: params) if people.is_a?(Array)
14
- return batch_get(api, params: params)
9
+ class << self
10
+ # @return [Boolean] `true` if the method is supported, `false` otherwise.
11
+ def valid_method?(value)
12
+ VALID_METHODS.include?(value)
13
+ end
14
+ end
15
+
16
+ # Gets the _people_ of the organization according `params`.
17
+ # If `people` is not `nil`, scopes to only the people specified.
18
+ # @note
19
+ # - If `people` is given keys `page:` and `q` of `params:`.
20
+ # @param people [Nil, People, Enumerable<Person>, Enumerable<Hash>] target _People_ to launch the batch against.
21
+ # @param params [Hash] api request options.
22
+ # @option params [String] :page the page number `page` based on `:per_page`.
23
+ # @option params [String] :per_page the number of people included per each batch api request.
24
+ # @option params [String] :q some text to search. Omit this parameter to target all the people.
25
+ # @return [Array<People>] all the people based on `params`
26
+ def get_people(people = nil, params: {})
27
+ return launch(people, method: :get, params: params) if people.is_a?(Enumerable)
28
+ return get(params: params)
15
29
  end
16
30
 
17
- def launch(people = nil, method:, api: nil, params: {per_page: DEFAULT_BATCH_BLOCK})
18
- fatal "Invalid batch method: #{method}." if !valid_method?(method)
19
- return nil if !people || !people.is_a?(Array)
20
- batch_from(people, method, api || self.api, params: params)
31
+ # launches a batch of `method` type using `people` and the specified `params`
32
+ # @raise Exception
33
+ # - if `people` is `nil` or is not an `Enumerable`.
34
+ # - if there's no `api` connection linked to the current `Batch`.
35
+ # @param people [People, Enumerable<Person>, Enumerable<Hash>] target _People_ to launch the batch against.
36
+ # @param method [Symbol] the method to launch the batch api request with.
37
+ # @param params [Hash] api request options.
38
+ # @option params [String] :per_page the number of people included per each batch api request.
39
+ # @return [BatchStatus] the `status` of this batch launch.
40
+ def launch(people, method:, params: {} , silent: false)
41
+ batch_from(people, method: method, params: params, silent: silent)
21
42
  end
22
43
 
23
- def valid_method?(value)
24
- VALID_METHODS.include?(value)
44
+ def search(data, params: {})
45
+ params = {per_page: DEFAULT_BATCH_BLOCK}.merge(params)
46
+
47
+ launch(data, method: :get, params: params, silent: true).tap do |status|
48
+ status.type = :search
49
+ status.queue.each do |entry|
50
+ unless status.success?(entry)
51
+ email = nil
52
+ case
53
+ when entry.respond_to?(:email)
54
+ email = entry.email
55
+ when entry.respond_to?(:to_h)
56
+ email = entry.to_h["email"]
57
+ end
58
+
59
+ people_matching = []
60
+ email = email.to_s.strip.downcase
61
+ unless email.empty?
62
+ people_matching = get(params: params.merge(q: email), silent: true).select do |person|
63
+ person.email == email
64
+ end
65
+ end
66
+
67
+ case people_matching.length
68
+ when 1
69
+ status.set_person_match(entry, people_matching.first)
70
+ when 2..Float::INFINITY
71
+ status.set_people_match(entry, people.matching)
72
+ end
73
+ end
74
+ end
75
+ end
25
76
  end
26
77
 
27
78
  private
@@ -30,38 +81,38 @@ module Eco
30
81
  BatchStatus.new(enviro, queue: queue, method: method)
31
82
  end
32
83
 
33
- def batch_get(api, params: {})
84
+ def get(params: {}, silent: false)
34
85
  fatal "cannot batch get without api connnection, please provide a valid api connection!" unless people_api = api&.people
35
86
 
36
87
  params = {per_page: DEFAULT_BATCH_BLOCK}.merge(params)
37
88
  client = people_api.client
38
- people = [];
39
- if page = params[:page]
40
- res, response = get(client, params: params)
41
- people +=res
42
- logger.info("page number: #{page}, got num people #{people.length}")
43
- else
44
- page = 1
45
- loop do
46
- params = params.merge({page: page})
47
- people_res, response = get(client, params: params)
48
- people += people_res
49
-
50
- total_pages = response.body["total_pages"]
51
- logger.info("page number: #{page}/#{total_pages}, got num people #{people_res.length}, with total #{people.length} people got")
52
- break if page >= total_pages
53
- page += 1
54
- end
89
+
90
+ looping = !params.key?(:page)
91
+ page = params[:page] || 1
92
+
93
+ people = []; total_pages = nil
94
+ loop do
95
+ people_res, response = client_get(client, params: params.merge(page: page), silent: silent)
96
+ people += people_res
97
+
98
+ total_pages ||= response.body["total_pages"]
99
+
100
+ msg = "page number: #{page}/#{total_pages}, got num people #{people_res.length}, with total #{people.length} people got"
101
+ logger.info(msg) unless silent
102
+
103
+ break if page >= total_pages || !looping
104
+ page += 1
55
105
  end
106
+
56
107
  return people
57
108
  end
58
109
 
59
- def get(client, params:)
110
+ def client_get(client, params:, silent: false)
60
111
  response = client.get("/people", params: params)
61
112
  unless response.success?
62
113
  msg = "Request failed - params: #{params}"
63
114
  msg += "\n Error message: - Status #{response.status}: #{response.body}"
64
- fatal(msg)
115
+ fatal msg
65
116
  end
66
117
  people = []
67
118
  response.body["results"].each do |person_hash|
@@ -72,40 +123,39 @@ module Eco
72
123
  [people, response]
73
124
  end
74
125
 
75
- def batch_from(people, method, api, params: {})
76
- return nil if !people || !people.is_a?(Array)
126
+ def batch_from(people, method:, params: {}, silent: false)
127
+ fatal "Invalid batch method: #{method}." if !self.class.valid_method?(method)
128
+ return nil if !people || !people.is_a?(Enumerable)
77
129
  fatal "cannot batch #{method} without api connnection, please provide a valid api connection!" unless people_api = api&.people
78
130
 
79
131
  # batch Status
80
132
  status = new_status(people, method)
81
133
 
82
134
  # param q does not make sense here, even for GET method
83
- params = {per_page: DEFAULT_BATCH_BLOCK}.merge(params)
84
- per_page = params.fetch(:per_page)
135
+ params = {per_page: DEFAULT_BATCH_BLOCK}.merge(params)
136
+ per_page = params[:per_page] || DEFAULT_BATCH_BLOCK
85
137
 
86
138
  iteration = 1; done = 0
87
139
  iterations = (people.length.to_f / per_page).ceil
88
140
 
89
141
  people.each_slice(per_page) do |slice|
90
142
  msg = "starting batch '#{method}' iteration #{iteration}/#{iterations}, with #{slice.length} entries of #{people.length} -- #{done} done"
91
- logger.info(msg)
143
+ logger.info(msg) unless silent
92
144
 
93
145
  people_api.batch do |batch|
94
146
  slice.each do |person|
95
- # valid method checked before
96
147
  batch.public_send(method, person) do |response|
148
+ faltal("Request with no response") unless !!response
97
149
  status[person] = response
98
- #status.print_error(person)
99
- end # current person
100
- end # next person
150
+ end
151
+ end
101
152
  end # next batch
102
153
 
103
154
  iteration += 1
104
155
  done += slice.length
105
156
  end # next slice
106
157
 
107
- status.print_errors
108
-
158
+ status.print_errors unless silent
109
159
  return status
110
160
  end
111
161
 
@@ -2,15 +2,26 @@ module Eco
2
2
  module API
3
3
  class Session
4
4
  class BatchJob < API::Common::Session::BaseSession
5
- TYPES = [:create, :update, :delete, :get]
5
+ TYPES = [:get, :create, :update, :delete]
6
6
  SETS = [:core, :details, :account]
7
7
 
8
8
  attr_reader :name, :status
9
9
 
10
+ class << self
11
+ def valid_type?(value)
12
+ TYPES.include?(value)
13
+ end
14
+
15
+ def valid_sets?(value)
16
+ sets = [value].flatten
17
+ sets.all? { |s| SETS.include?(s) }
18
+ end
19
+ end
20
+
10
21
  def initialize(e, name:, type:, sets:)
11
22
  raise "A name is required to refer a job. Given: #{name}" if !name
12
- raise "Type should be one of #{TYPES}. Given: #{type}" if !BatchJob.valid_type?(type)
13
- raise "Sets should be some of #{SETS}. Given: #{sets}" if !BatchJob.valid_sets?(sets)
23
+ raise "Type should be one of #{TYPES}. Given: #{type}" if !self.class.valid_type?(type)
24
+ raise "Sets should be some of #{SETS}. Given: #{sets}" if !self.class.valid_sets?(sets)
14
25
  super(e)
15
26
 
16
27
  @name = name
@@ -20,9 +31,10 @@ module Eco
20
31
  end
21
32
 
22
33
  def reset
23
- @queue = []
24
- @callbacks = {}
25
- @status = nil
34
+ @queue = []
35
+ @queue_hash = {}
36
+ @callbacks = {}
37
+ @status = nil
26
38
  end
27
39
 
28
40
  def signature
@@ -38,82 +50,130 @@ module Eco
38
50
  @queue.length > 0
39
51
  end
40
52
 
41
- def add(entry)
53
+ def core?
54
+ sets.include?(:core)
55
+ end
56
+
57
+ def details?
58
+ sets.include?(:details)
59
+ end
60
+
61
+ def account?
62
+ sets.include?(:account)
63
+ end
64
+
65
+ # Adds an entry to the job queue.
66
+ # @param entry [Person] the person we want to update, carrying the changes to be done.
67
+ # @param unique [Boolean] specifies if repeated entries should be avoided in the queue.
68
+ # @yield [person] callback before launching the batch job request against the server.
69
+ # @yeldparam param [Person] current person object that that should be treated by the callback before launching the batch.
70
+ # @return [Void]
71
+ def add(entry, unique: true)
42
72
  unless !entry
43
- @queue.push(entry)
44
- @callbacks[entry] = Proc.new if block_given?
73
+ unless unique && @queue_hash.key?(entry)
74
+ @queue_hash[entry] = true
75
+ @queue.push(entry)
76
+ @callbacks[entry] = Proc.new if block_given?
77
+ end
45
78
  end
46
79
  end
47
80
 
48
- def people
49
- Eco::API::Organization::People.new(@queue)
81
+ def people(input = @queue)
82
+ Eco::API::Organization::People.new(input)
50
83
  end
51
84
 
52
85
  def processed_queue
53
- @queue.map do |entry|
54
- callback = @callbacks[entry]
55
- e = entry
56
- e = callback.call(entry) if callback
57
- e = nil if as_update(e).empty? && @type!=:delete
86
+ pre_queue = @queue.map do |e|
87
+ if callback = @callbacks[e]
88
+ callback.call(e)
89
+ end
90
+ e = nil if as_update(e).empty?
58
91
  e
59
92
  end.compact
93
+ apply_policies(pre_queue)
94
+ end
95
+
96
+ def processed_queue
97
+ @queue.each {|e| @callbacks[e].call(e) if @callbacks.key?(e) }
98
+ apply_policies(@queue).select {|e| !as_update(e).empty?}
60
99
  end
61
100
 
62
101
  def launch(simulate: false)
63
102
  queue = processed_queue
64
103
  launch_feedback(queue, simulate ? 2500 : 800)
65
104
 
66
- if !simulate && queue.length > 0
67
- backup_update(queue)
68
- @status = session.batch.launch(queue, method: @type.to_s)
69
- @status.root = self
105
+ if !simulate
106
+ if queue.length > 0
107
+ backup_update(queue)
108
+ @status = session.batch.launch(queue, method: @type)
109
+ @status.root = self
110
+ end
70
111
  end
71
112
 
72
- logger.info("Simulate: this would have launched: '#{@type.to_s}'") if simulate
113
+ post_launch(queue: queue, simulate: simulate)
114
+
115
+ logger.info("Simulate: this would have launched: '#{@type}'") if simulate
73
116
  return @status
74
117
  end
75
118
 
76
- def core?
77
- sets.include?(:core)
78
- end
79
119
 
80
- def details?
81
- sets.include?(:dettails)
82
- end
83
-
84
- def account?
85
- sets.include?(:account)
86
- end
120
+ private
87
121
 
88
- def self.valid_type?(value)
89
- TYPES.include?(value)
122
+ def post_launch(queue: [], simulate: false)
123
+ if !simulate && @status
124
+ @status.queue.map do |entry|
125
+ if @status.success?(entry)
126
+ entry.consolidate! if entry.respond_to?(:consolidate!)
127
+ #else # shouldn't probably reset, as the model remains dirty? (well tracaked)
128
+ # entry.reset! if entry.respond_to?(:reset!)
129
+ end
130
+ end
131
+ elsif simulate
132
+ queue.map do |entry|
133
+ entry.consolidate! if entry.respond_to?(:consolidate!)
134
+ end
135
+ end
90
136
  end
91
137
 
92
- def self.valid_sets?(value)
93
- sets = [value].flatten
94
- sets.all? { |s| SETS.include?(s) }
138
+ def apply_policies(pre_queue)
139
+ pre_queue.tap do |entries|
140
+ policies = session.config.api_policies.policies
141
+ unless policies.empty?
142
+ policies.launch(people: people(entries), session: session)
143
+ end
144
+ end
95
145
  end
96
146
 
97
- private
98
-
99
- def as_update(update)
100
- hash = update if update.is_a?(Hash)
101
- if @type == :delete
102
- hash = update.as_json.slice("id", "external_id")
147
+ def as_update(entry)
148
+ hash = entry if entry.is_a?(Hash)
149
+ if only_ids?
150
+ hash = entry.as_json.slice("id", "external_id", "email")
103
151
  else
104
- hash = update.as_update if update.is_a?(Ecoportal::API::V1::Person)
152
+ if entry.is_a?(Ecoportal::API::V1::Person)
153
+ hash = entry.as_update
154
+ if hfields = hash.dig("details", "fields")
155
+ hash["details"]["fields"] = hfields.map do |fld|
156
+ fld.merge!("alt_id" => entry.details.get_field(fld["id"]).alt_id) if entry.details
157
+ end
158
+ end
159
+ end
160
+
105
161
  fields = hash&.dig('details', 'fields')
106
162
  fields&.map! { |fld| fld&.slice("id", "alt_id", "value") }
107
163
  end
108
164
  hash || {}
109
165
  end
110
166
 
167
+ def only_ids?
168
+ [:delete, :get].include?(@type)
169
+ end
170
+
111
171
  def sets_title
112
172
  "#{@sets.map {|s| s.to_s}.join(", ")}"
113
173
  end
114
174
 
115
175
  def launch_feedback(data, max_chars = 800)
116
- if !data || !data.is_a?(Array) || data.empty?
176
+ if !data || !data.is_a?(Enumerable) || data.empty?
117
177
  logger.warn("#{"*" * 20} Nothing for #{signature} so far :) #{"*" * 20}")
118
178
  return
119
179
  end
@@ -122,8 +182,8 @@ module Eco
122
182
 
123
183
  sample_length = 1
124
184
  sample = data.slice(0, 20).map do |entry|
125
- update = as_update(entry)
126
- max_chars -= update.pretty_inspect.length
185
+ update = as_update(entry)
186
+ max_chars -= update.pretty_inspect.length
127
187
  sample_length += 1 if max_chars > 0
128
188
  update
129
189
  end
@@ -135,8 +195,8 @@ module Eco
135
195
 
136
196
  def backup_update(data)
137
197
  data_body = data.map { |u| as_update(u) }
138
- dir = config.people.requests_folder
139
- file = File.join(dir, "#{@type.to_s}_data.json")
198
+ dir = config.people.requests_folder
199
+ file = File.join(dir, "#{@type}_data.json")
140
200
  file_manager.save_json(data_body, file, :timestamp)
141
201
  end
142
202
 
@@ -26,11 +26,10 @@ module Eco
26
26
  def new(name, type:, sets:)
27
27
  fatal "Can't create job named '#{name}' because it already exists." if exists?(name)
28
28
 
29
- job = BatchJob.new(enviro, name: name, type: type, sets: sets)
30
- @jobs[name] = job
31
- @callbacks[job] = Proc.new if block_given?
32
-
33
- job
29
+ BatchJob.new(enviro, name: name, type: type, sets: sets).tap do |job|
30
+ @jobs[name] = job
31
+ @callbacks[job] = Proc.new if block_given?
32
+ end
34
33
  end
35
34
 
36
35
  def pending?