ecoportal-api 0.9.8 → 0.10.2

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.
@@ -1,24 +1,26 @@
1
1
  module Ecoportal
2
2
  module API
3
3
  class Logger
4
- TIMESTAMP_PATTERN = "%Y-%m-%dT%H:%M:%S"
5
- STDOUT_FORMAT_PROC = proc do |severity, datetime, progname, msg|
6
- prefix = "%5s > " % severity
4
+ TIMESTAMP_PATTERN = "%Y-%m-%dT%H:%M:%S".freeze
5
+
6
+ STDOUT_FORMAT_PROC = proc do |severity, _datetime, _progname, msg|
7
+ prefix = "%5s > " % severity # rubocop:disable Style/FormatString
7
8
  msg.lines.map.with_index do |line, idx|
8
9
  if idx.zero?
9
10
  prefix + line.chomp
10
11
  else
11
- " "*prefix.length + line.chomp
12
+ (" " * prefix.length) + line.chomp
12
13
  end
13
14
  end.join("\n")+"\n"
14
15
  end
15
- FILE_FORMAT_PROC = proc do |severity, datetime, progname, msg|
16
- prefix = "%5s(%s) > " % [severity, datetime.strftime(TIMESTAMP_PATTERN)]
16
+
17
+ FILE_FORMAT_PROC = proc do |severity, datetime, _progname, msg|
18
+ prefix = "%5s(%s) > " % [severity, datetime.strftime(TIMESTAMP_PATTERN)] # rubocop:disable Style/FormatString, Style/FormatStringToken
17
19
  msg.lines.map.with_index do |line, idx|
18
20
  if idx.zero?
19
21
  prefix + line.chomp
20
22
  else
21
- " "*prefix.length + line.chomp
23
+ (" " * prefix.length) + line.chomp
22
24
  end
23
25
  end.join("\n")+"\n"
24
26
  end
@@ -34,7 +36,7 @@ module Ecoportal
34
36
  @file = make_file_logger(file_level, output_file)
35
37
  end
36
38
 
37
- %w(unknown fatal error warn info debug).each do |type|
39
+ %w[unknown fatal error warn info debug].each do |type|
38
40
  define_method(type) do |&block|
39
41
  @console.send(type, &block)
40
42
  @file&.send(type, &block)
@@ -44,7 +46,7 @@ module Ecoportal
44
46
  private
45
47
 
46
48
  def make_stdout_logger(level)
47
- ::Logger.new(STDOUT).tap do |logger|
49
+ ::Logger.new($stdout).tap do |logger|
48
50
  logger.formatter = STDOUT_FORMAT_PROC
49
51
  logger.level = level
50
52
  end
@@ -0,0 +1,33 @@
1
+ module Ecoportal
2
+ module API
3
+ class V1
4
+ class JobStatus
5
+ attr_reader :id, :progress
6
+
7
+ def initialize(id, complete, errored, progress)
8
+ @id = id
9
+ @complete = complete
10
+ @errored = errored
11
+ @progress = progress
12
+ end
13
+
14
+ def complete?(total = nil)
15
+ return @complete if total.nil?
16
+
17
+ progress >= total
18
+ end
19
+
20
+ def errored?
21
+ @errored
22
+ end
23
+
24
+ def to_s
25
+ msg = complete? ? "Completed" : "In progress"
26
+ msg = "Errored" if errored?
27
+ msg << " with #{progress} done."
28
+ msg
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,13 +1,14 @@
1
1
  module Ecoportal
2
2
  module API
3
3
  class V1
4
- # @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
4
+ # @attr_reader client [Common::Client] a `Common::Client` object that
5
+ # holds the configuration of the api connection.
5
6
  class People
6
7
  extend Common::BaseClass
7
- include Enumerable
8
8
  include Common::DocHelpers
9
+ include Common::TimeOut
10
+ include Enumerable
9
11
 
10
- JOB_TIMEOUT = 240
11
12
  DELAY_STATUS_CHECK = 5
12
13
 
13
14
  class_resolver :person_class, "Ecoportal::API::V1::Person"
@@ -30,35 +31,49 @@ module Ecoportal
30
31
  # @param silent [Boolean] `false` to show percentage of progress.
31
32
  # @yield [person] does some stuff with the person.
32
33
  # @yieldparam person [Person]
33
- def each(params: {}, silent: false, &block)
34
- return to_enum(:each, params: params, silent: silent) unless block
35
- cursor_id = nil; results = 0
34
+ def each(params: {}, silent: false) # rubocop:disable Metrics/AbcSize
35
+ return to_enum(:each, params: params, silent: silent) unless block_given?
36
+
37
+ cursor_id = nil
38
+ results = 0
39
+
36
40
  puts "\n" unless silent
41
+
37
42
  loop do
38
43
  params.update(cursor_id: cursor_id) if cursor_id
39
- body = nil; response = nil; count = 5
44
+
45
+ body = nil
46
+ response = nil
47
+ count = 5
48
+
40
49
  loop do
41
50
  response = client.get("/people", params: params)
42
51
  body = response && body_data(response.body)
43
52
  break if response.success? || count <= 0
53
+
44
54
  puts "Request failed - Status #{response.status}: #{body}"
45
55
  count -= 1
56
+
46
57
  sleep(0.5)
47
58
  end
59
+
48
60
  raise "Request failed - Status #{response.status}: #{body}" unless response.success?
49
61
 
50
- unless silent || (total = body["total_results"]) == 0
62
+ unless silent || (total = body["total_results"])&.zero?
51
63
  results += body["results"].length
52
64
  percent = results * 100 / total
65
+
53
66
  msg = "People GET"
54
- msg += " (search=#{params[:q]})" if params.key?(:q)
55
- print "#{msg}: #{percent.round}% (of #{total})\r"
67
+ msg << " (search=#{params[:q]})" if params.key?(:q)
68
+
69
+ print "#{msg}: #{percent.round}% (of #{total}): #{results}\r"
56
70
  $stdout.flush
57
71
  end
58
72
 
59
73
  body["results"].each do |person|
60
74
  yield person_class.new(person)
61
75
  end
76
+
62
77
  break unless (cursor_id = body["cursor_id"])
63
78
  end
64
79
  self
@@ -81,9 +96,10 @@ module Ecoportal
81
96
  # @return [Person] the person with `id` (internal or external) contained in `doc`.
82
97
  def get(doc)
83
98
  id = get_id(doc)
84
- response = client.get("/people/"+CGI.escape(id))
99
+ response = client.get("/people/#{CGI.escape(id)}")
85
100
  body = body_data(response.body)
86
101
  return person_class.new(body) if response.success?
102
+
87
103
  raise "Could not get person #{id} - Error #{response.status}: #{body}"
88
104
  end
89
105
 
@@ -93,7 +109,7 @@ module Ecoportal
93
109
  def update(doc)
94
110
  body = get_body(doc)
95
111
  id = get_id(doc)
96
- client.patch("/people/"+CGI.escape(id), data: body)
112
+ client.patch("/people/#{CGI.escape(id)}", data: body)
97
113
  end
98
114
 
99
115
  # Requests to create a person via api.
@@ -110,7 +126,7 @@ module Ecoportal
110
126
  def upsert(doc)
111
127
  body = get_body(doc)
112
128
  id = get_id(doc)
113
- client.post("/people/"+CGI.escape(id), data: body)
129
+ client.post("/people/#{CGI.escape(id)}", data: body)
114
130
  end
115
131
 
116
132
  # Requests to completelly remove from an organization an existing person via api.
@@ -118,7 +134,7 @@ module Ecoportal
118
134
  # @return [Response] an object with the api response.
119
135
  def delete(doc)
120
136
  id = get_id(doc)
121
- client.delete("/people/"+CGI.escape(id))
137
+ client.delete("/people/#{CGI.escape(id)}")
122
138
  end
123
139
 
124
140
  # Creates a `BatchOperation` and yields it to the given bock.
@@ -128,8 +144,15 @@ module Ecoportal
128
144
  # @return [Ecoportal::API::Common::Response] the results of the batch
129
145
  def batch(job_mode: true, &block)
130
146
  return job(&block) if job_mode
131
- operation = Common::BatchOperation.new("/people", person_class, logger: client.logger)
147
+
148
+ operation = Common::BatchOperation.new(
149
+ "/people",
150
+ person_class,
151
+ logger: client.logger
152
+ )
153
+
132
154
  yield operation
155
+
133
156
  # The batch operation is responsible for logging the output
134
157
  client.post("/people/batch", data: operation.as_json).tap do |response|
135
158
  operation.process_response(response)
@@ -138,16 +161,30 @@ module Ecoportal
138
161
 
139
162
  # @return [Ecoportal::API::Common::Response] the results of the batch job
140
163
  def job
141
- operation = Common::BatchOperation.new("/people", person_class, logger: client.logger)
164
+ operation = Common::BatchOperation.new(
165
+ "/people",
166
+ person_class,
167
+ logger: client.logger
168
+ )
169
+
142
170
  yield operation
143
- job_id = create_job(operation)
144
- status = wait_for_job_completion(job_id)
145
171
 
146
- if status&.complete?
172
+ total = operation.count
173
+ timeout = timeout_for(total)
174
+
175
+ job_id = create_job(operation)
176
+ status = wait_for_job_completion(job_id, timeout: timeout, total: total)
177
+
178
+ # @todo
179
+ # if total == status.progress
180
+ if status&.complete?(total)
147
181
  job_result(job_id, operation)
148
182
  else
149
- msg = "Job `#{job_id}` not complete. Probably timeout after #{JOB_TIMEOUT} seconds. Current status: #{status}"
150
- raise API::Errors::TimeOut.new msg
183
+ msg = "Job '#{job_id}' not complete (size: #{total}).\n"
184
+ msg << " Probably timeout after #{timeout} seconds.\n"
185
+ msg << " Current status: #{status}"
186
+
187
+ raise API::Errors::TimeOut, msg
151
188
  end
152
189
  end
153
190
 
@@ -159,17 +196,15 @@ module Ecoportal
159
196
 
160
197
  private
161
198
 
162
- JobStatus = Struct.new(:id, :complete?, :errored?, :progress)
163
199
  def job_status(job_id)
164
200
  response = client.get("/people/job/#{CGI.escape(job_id)}/status")
165
201
  body = response && body_data(response.body)
166
- raise "Status error (#{response.status}) - Errors: #{body}" unless response.success?
167
- JobStatus.new(
168
- body["id"],
169
- body["complete"],
170
- body["errored"],
171
- body["progress"]
172
- )
202
+
203
+ msg = "Status error (#{response.status}) - "
204
+ msg << "Errors: #{body}"
205
+ raise msg unless response.success?
206
+
207
+ JobStatus.new(*body.values_at(*%w[id complete errored progress]))
173
208
  end
174
209
 
175
210
  # @return [Ecoportal::API::Common::Response] the results of the batch job
@@ -179,14 +214,26 @@ module Ecoportal
179
214
  end
180
215
  end
181
216
 
182
- def wait_for_job_completion(job_id)
217
+ def wait_for_job_completion(job_id, timeout:, total:)
183
218
  # timeout library is evil. So we make poor-man timeout.
184
219
  # https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
185
220
  before = Time.now
186
- while true
221
+
222
+ loop do
187
223
  status = job_status(job_id)
188
- break status if status.complete?
189
- break status if Time.now >= before + JOB_TIMEOUT
224
+ break status if status.complete?(total)
225
+
226
+ left = (before + timeout) - Time.now
227
+ break status unless left.positive?
228
+ # break status if Time.now >= before + timeout
229
+
230
+ msg = " ... Await job "
231
+ msg << "('#{job_id}'; done: #{status.progress}): "
232
+ msg << "#{left.ceil} sec. \r"
233
+
234
+ print msg
235
+ $stdout.flush
236
+
190
237
  sleep(DELAY_STATUS_CHECK)
191
238
  status
192
239
  end
@@ -207,7 +254,6 @@ module Ecoportal
207
254
  def body_data(body)
208
255
  body
209
256
  end
210
-
211
257
  end
212
258
  end
213
259
  end
@@ -9,14 +9,16 @@ module Ecoportal
9
9
  # @attr details [PersonDetails, nil] the details of the person or `nil` if missing.
10
10
  class Person < Common::BaseModel
11
11
  passthrough :id, :external_id, :name, :email, :filter_tags
12
+ passthrough :archived
12
13
  passthrough :supervisor_id, :contractor_organization_id
14
+ passthrough :brand_id
13
15
  passthrough :freemium
14
16
 
15
17
  class_resolver :person_schema_class, "Ecoportal::API::V1::PersonSchema"
16
18
  class_resolver :person_details_class, "Ecoportal::API::V1::PersonDetails"
17
19
  embeds_one :details, nullable: true, klass: :person_details_class
18
20
 
19
- VALID_TAG_REGEX = /^[A-Za-z0-9 &_'\/.-]+$/
21
+ VALID_TAG_REGEX = /^[A-Za-z0-9 &_'\/.-]+$/
20
22
  VALID_EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/
21
23
 
22
24
  # Gets the supervisor (`Person`) of this person, with given his `supervisor_id`.
@@ -47,9 +49,8 @@ module Ecoportal
47
49
  # Sets the email of a person.
48
50
  # @param value [String, nil] the email of this person.
49
51
  def email=(value)
50
- unless !value || value.match(VALID_EMAIL_REGEX)
51
- raise "Invalid email #{value.inspect}"
52
- end
52
+ raise "Invalid email #{value.inspect}" if value && !value.match(VALID_EMAIL_REGEX)
53
+
53
54
  doc["email"] = value&.downcase
54
55
  end
55
56
 
@@ -58,15 +59,16 @@ module Ecoportal
58
59
  # @raise [Exception] if there was any invalid string tag.
59
60
  # @param value [Array<String>] array of tags.
60
61
  def filter_tags=(value)
61
- unless value.is_a?(Array)
62
- raise "filter_tags= needs to be passed an Array, got #{value.class}"
63
- end
62
+ msg = "filter_tags= needs to be passed an Array, got #{value.class}"
63
+ raise ArgumentError, msg unless value.is_a?(Array)
64
+
64
65
  end_tags = value.compact.map do |tag|
65
- unless tag.match(VALID_TAG_REGEX)
66
- raise "Invalid filter tag #{tag.inspect}"
67
- end
66
+ msg = "Invalid filter tag #{tag.inspect}"
67
+ raise ArgumentError, msg unless tag.match(VALID_TAG_REGEX)
68
+
68
69
  tag.upcase
69
70
  end
71
+
70
72
  set_uniq_array_keep_order("filter_tags", end_tags)
71
73
  end
72
74
 
@@ -105,8 +107,10 @@ module Ecoportal
105
107
  end
106
108
 
107
109
  # Sets the PersonDetails to the person, depending on the parameter received:
108
- # - `PersonSchema`: initializes the `PersonDetails` as per the schema specified (`schema_id` and `fields`).
109
- # - `String`: it just sets the `schema_id` on the `PersonDetails` (as `fields` is not include, `details[key]=` will throw error).
110
+ # - `PersonSchema`: initializes the `PersonDetails` as per the schema specified
111
+ # (`schema_id` and `fields`).
112
+ # - `String`: it just sets the `schema_id` on the `PersonDetails`
113
+ # (as `fields` is not include, `details[key]=` will throw error).
110
114
  # (see #details=)
111
115
  # @note
112
116
  # - this method alone only sets the internal structure of the details.
@@ -130,7 +134,6 @@ module Ecoportal
130
134
  }
131
135
  end
132
136
  end
133
-
134
137
  end
135
138
  end
136
139
  end
@@ -40,7 +40,8 @@ module Ecoportal
40
40
 
41
41
  # Gets the value of one specific field of the PersonDetails.
42
42
  # @param id [String] the `id` or the `alt_id` of the target field.
43
- # @return [String, Array<String>, Boolean, Array<Boolean>, Date, Array<Date>, Numberic, Array<Numeric>] the value of field or `nil` if missing.
43
+ # @return [String, Array<String>, Boolean, Array<Boolean>, Date, Array<Date>, Numberic, Array<Numeric>]
44
+ # the value of field or `nil` if missing.
44
45
  def [](id)
45
46
  get_field(id)&.value
46
47
  end
@@ -50,11 +51,10 @@ module Ecoportal
50
51
  # @param id [String] the `id` or the `alt_id` of the target field.
51
52
  # @return [void]
52
53
  def []=(id, value)
53
- if field = get_field(id)
54
- field.value = value
55
- else
56
- raise MissingId.new("details[#{id.inspect}] is missing. Did you forget to load the schema?")
57
- end
54
+ msg = "details[#{id.inspect}] is missing. Did you forget to load the schema?"
55
+ raise MissingId, msg unless (field = get_field(id))
56
+
57
+ field.value = value
58
58
  end
59
59
 
60
60
  # Checks if an `id` or `alt_id` exists
@@ -67,12 +67,14 @@ module Ecoportal
67
67
 
68
68
  # @return [Boolean] `true` if `id` exists and `value` has changed, `false` otherwise
69
69
  def changed?(id, doc = :original)
70
- return false unless field = get_field(id)
70
+ return false unless (field = get_field(id))
71
+
71
72
  field.as_update.key?("value")
72
73
  end
73
74
 
74
75
  def original_value(id)
75
- return nil unless field = get_field(id)
76
+ return nil unless (field = get_field(id))
77
+
76
78
  field.original_doc["value"]
77
79
  end
78
80
 
@@ -87,7 +89,6 @@ module Ecoportal
87
89
  @fields_by_alt_id[wrapped.alt_id] = wrapped
88
90
  end
89
91
  end
90
-
91
92
  end
92
93
  end
93
94
  end
@@ -30,6 +30,7 @@ module Ecoportal
30
30
  def index_fields
31
31
  @fields_by_id = {}
32
32
  @fields_by_alt_id = {}
33
+
33
34
  doc["fields"].each do |field|
34
35
  wrapped = schema_field_class.new(field)
35
36
  @fields_by_id[wrapped.id] = wrapped
@@ -45,7 +46,6 @@ module Ecoportal
45
46
  )
46
47
  end
47
48
  end
48
-
49
49
  end
50
50
  end
51
51
  end
@@ -38,8 +38,6 @@ module Ecoportal
38
38
  return to_enum(:each) unless block
39
39
  get_all.each(&block)
40
40
  end
41
-
42
-
43
41
  end
44
42
  end
45
43
  end
@@ -22,11 +22,10 @@ module Ecoportal
22
22
  Date.parse(line) rescue return nil, false
23
23
  end
24
24
  end.compact
25
- if multiple
26
- return values, true
27
- else
28
- return values.first, true
29
- end
25
+
26
+ out = values
27
+ out = values.first unless multiple
28
+ [out, true]
30
29
  end
31
30
  end
32
31
  end
@@ -6,29 +6,27 @@ module Ecoportal
6
6
  passthrough :id, :alt_id, :type, :name, :shared, :multiple
7
7
 
8
8
  def clear
9
- if multiple
10
- doc["value"] = []
11
- else
12
- doc["value"] = nil
13
- end
9
+ return doc["value"] = [] if multiple
10
+
11
+ doc["value"] = nil
14
12
  end
15
13
 
16
14
  def value
17
15
  case type
18
16
  when "text", "phone_number", "number", "boolean", "select"
19
- doc["value"]
17
+ doc["value"]
20
18
  when "date"
21
- if doc["value"]
22
- maybe_multiple(doc["value"]) do |v|
23
- Date.iso8601(v)
24
- end
25
- end
19
+ if doc["value"]
20
+ maybe_multiple(doc["value"]) do |v|
21
+ Date.iso8601(v)
22
+ end
23
+ end
26
24
  else
27
- raise "Unknown type #{type}"
25
+ raise "Unknown type #{type}"
28
26
  end
29
27
  end
30
28
 
31
- def value=(value)
29
+ def value=(value) # rubocop:disable Metrics/AbcSize
32
30
  case type
33
31
  when "text", "phone_number", "select"
34
32
  doc["value"] = maybe_multiple(value) do |v|
@@ -36,19 +34,21 @@ module Ecoportal
36
34
  end
37
35
  when "number"
38
36
  maybe_multiple(value) do |v|
39
- unless v.nil? || v.is_a?(Numeric)
40
- raise "Invalid number type #{v.class}"
41
- end
37
+ next if v.nil? || v.is_a?(Numeric)
38
+
39
+ raise "Invalid number type #{v.class}"
42
40
  end
41
+
43
42
  doc["value"] = value
44
43
  when "boolean"
45
44
  doc["value"] = !!value
46
45
  when "date"
47
46
  maybe_multiple(value) do |v|
48
- unless v.nil? || v.respond_to?(:to_date)
49
- raise "Invalid date type #{v.class}"
50
- end
47
+ next if v.nil? || v.respond_to?(:to_date)
48
+
49
+ raise "Invalid date type #{v.class}"
51
50
  end
51
+
52
52
  doc["value"] = maybe_multiple(value) do |v|
53
53
  v&.to_date&.to_s
54
54
  end
@@ -57,12 +57,11 @@ module Ecoportal
57
57
  end
58
58
  end
59
59
 
60
- def maybe_multiple(value)
60
+ def maybe_multiple(value, &block)
61
61
  if multiple
62
- unless value.is_a?(Array)
63
- raise "Expected Array, got #{value.class}"
64
- end
65
- value.map {|v| yield v }
62
+ raise "Expected Array, got #{value.class}" unless value.is_a?(Array)
63
+
64
+ value.map(&block)
66
65
  else
67
66
  yield value
68
67
  end
@@ -6,9 +6,10 @@ module Ecoportal
6
6
  extend Common::BaseClass
7
7
  include Common::Logging
8
8
 
9
- VERSION = "v1"
10
- class_resolver :people_class, "Ecoportal::API::V1::People"
11
- class_resolver :person_schemas_class, "Ecoportal::API::V1::PersonSchemas"
9
+ VERSION = "v1".freeze
10
+
11
+ class_resolver :people_class, "Ecoportal::API::V1::People"
12
+ class_resolver :person_schemas_class, "Ecoportal::API::V1::PersonSchemas"
12
13
 
13
14
  attr_reader :client, :logger
14
15
 
@@ -45,5 +46,6 @@ module Ecoportal
45
46
  end
46
47
  end
47
48
 
49
+ require 'ecoportal/api/v1/job_status'
48
50
  require 'ecoportal/api/v1/person_schemas'
49
51
  require 'ecoportal/api/v1/people'
@@ -1,5 +1,5 @@
1
1
  module Ecoportal
2
2
  module API
3
- VERSION = "0.9.8".freeze
3
+ VERSION = '0.10.2'.freeze
4
4
  end
5
5
  end