ecoportal-api 0.8.2 → 0.8.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba5ffaf73f0c065f6aa6572b5ac82132af9ee19f61692f80605ee156b29d24ee
4
- data.tar.gz: d82d89d09d4ae1edb88a3dc01f613478e7bd5ab38184dadb7c507e53e65bae54
3
+ metadata.gz: a66f25a41e7da2e5d0d38efe75ef485e8c4e830a7af80d458170e1f9e44352a9
4
+ data.tar.gz: a3b1060723814238dc9b4bbfaa4098b0ae8d8f17320ba4dc9b9c3e33a0a9095c
5
5
  SHA512:
6
- metadata.gz: f3b637bd2bc2970b7f46eb8d92edc6b83a38ef4489d6d185e4cea47d8a5e6cd774d9bf9e8712bd0d76aa9a52c770e89207f0c7af283fcb85bc1cb481d31fca10
7
- data.tar.gz: 6a5dd4dd6fd2691971c526e79d2278a84351484a0d9ffdd93e3604392f4b59e21515255001c51016835a37506496d1d292f33c4dc69d4db7042e9c3d521721c3
6
+ metadata.gz: e0fe445e561bbefac97f7fb2eaa7e7b926f60310a0f98d9304f214321c039b04cf610cedeb46b0e5b2ba763139feaba5385df7f4dc2f855327a2f45b03f16888
7
+ data.tar.gz: e121969d12d78aded1030e8ff07ef92ab5d71f73b69bc05e4332ff8cc42f7f6e6d43bb6c9143ddfd5a523558e6b928f96d36af2973c0550b1ae9dc200ab149c3
data/CHANGELOG.md CHANGED
@@ -1,7 +1,49 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- ## [0.8.2] - 2021-02-xx
4
+
5
+ ## [0.8.3] - 2021-05-xx
6
+
7
+ ### Added
8
+ - `Ecoportal::API::Errors` namespace
9
+ - `Ecoportal::API::Errors::Base` base error class.
10
+ - `Ecoportal::API::Errors::TimeOut` error when an api request fails with time out.
11
+ - This serves the purpose to allow a client script to re-start the process where it stopped by capturing this specific Error
12
+ - `Ecoportal::API::Common::BaseModel::UnlinkedModel` added more description to track down the source of the error.
13
+ - `Ecoportal::API::Common::BaseModel#reset!` added parameter `key`, which should try to recover `doc[key]` from `original_doc[key]`
14
+ - Thanks to this, you are supposed to be able to do things like:
15
+ - `person.account = nil && person.reset!("account")`
16
+ - `person.name = nil && person.reset!("name")`
17
+ - `Ecoportal::API::V1::People#job` methods to provide more information on failure.
18
+ - Specific changes due to eP **release `1.5.9.70`** (_Policy Group Abilities_)
19
+ - `Ecoportal::API::Internal::Account#permissions_merged`
20
+ - `Ecoportal::API::Internal::Permissions#person_abilities` (new ability)
21
+ - `Ecoportal::API::Internal::Account#user_id`
22
+
23
+ ### Fixed
24
+ - `Ecoportal::API::Internal::Account`: consistency in setting arrays (`uniq!` & `compact`)
25
+ - `#policy_group_ids=`, `#login_provider_ids=`, `#starred_ids=`
26
+ - `Ecoportal::API::Common::HashDiff.diff` was including empty `{}` objects
27
+ - This change sacrifices the case `account: {}` (which will be also removed from `as_update`), but it should be fine.
28
+
29
+ ### Changed
30
+ - `Ecoportal::API::V1::People#job` to raise specific error on time out `API::Errors::TimeOut`
31
+ - Specific changes due to eP **release `1.5.9.70`** (_Policy Group Abilities_)
32
+ - `Ecoportal::API::Internal::Account` **removed** methods: `#permissions_preset`, `#preset` and `#preset=`
33
+ - `Ecoportal::API::Internal::Person#account=` added support for `user_id` which should remain unchanged when existing
34
+ - **remove** from `as_update` **read-only** data
35
+ - `Ecoportal::API::Common::HashDiff.diff` added parameter `:ignore` (`Array`)
36
+ - `Ecoportal::API::Common::BaseModel#as_update` added parameter `:ignore`
37
+ - `Ecoportal::API::V1::Person#as_update` added method, which ignores `subordinates`
38
+ - `Ecoportal::API::Internal::Person#as_update` added method, which ignores `user_id`, `permissions_merged` and `prefilter`
39
+ - `Ecoportal::API::Internal::Account#as_update` added method, which ignores `user_id`, `permissions_merged` and `prefilter`
40
+ - `Ecoportal::API::Common::Client` native support for `elastic-apm`
41
+ - Via new module `Ecoportal::API::Common::ElasticApmIntegration` with method `log_unexpected_server_error`, which will only log an `UnexpectedServerError` to _ElasticAPM_ if
42
+ 1. There's a correct configuration: environmental variables `ELASTIC_APM_KEY` and `ELASTIC_APM_ACCOUNT_ID` are defined
43
+ 2. The `Response` from the server gave `code` in the range `5xx` (which are those under server responsibility)
44
+ - `Ecoportal::API::Common::Client` added retry logics when `response.status == 5xx`
45
+
46
+ ## [0.8.2] - 2021-02-24
5
47
 
6
48
  ### Added
7
49
 
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.add_development_dependency "bundler", ">= 2.2.11", "< 2.3"
25
+ spec.add_development_dependency "bundler", ">= 2.2.17", "< 2.3"
26
26
  spec.add_development_dependency "rspec", ">= 3.10.0", "< 3.11"
27
27
  spec.add_development_dependency "rake", ">= 13.0.3", "< 13.1"
28
28
  spec.add_development_dependency "yard", ">= 0.9.26", "< 0.10"
@@ -30,5 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "pry" , "~> 0.14"
31
31
 
32
32
  spec.add_dependency 'http', '~> 4.4.1', "< 5"
33
+ spec.add_dependency 'dotenv', '>= 2.7.6', "< 2.8"
34
+ spec.add_dependency 'elastic-apm', '>= 4.0.0', "< 4.1"
33
35
  spec.add_dependency 'hash-polyfill', '~> 0'
34
36
  end
data/lib/ecoportal/api.rb CHANGED
@@ -2,6 +2,7 @@ require "cgi"
2
2
  require "logger"
3
3
  require "hash-polyfill"
4
4
  require "ecoportal/api/version"
5
+ require "dotenv/load"
5
6
 
6
7
  module Ecoportal
7
8
  module API
@@ -10,5 +11,6 @@ end
10
11
 
11
12
  require "ecoportal/api/logger"
12
13
  require "ecoportal/api/common"
14
+ require "ecoportal/api/errors"
13
15
  require "ecoportal/api/v1"
14
16
  require "ecoportal/api/internal"
@@ -10,6 +10,7 @@ require 'ecoportal/api/common/hash_diff'
10
10
  require 'ecoportal/api/common/base_model'
11
11
  require 'ecoportal/api/common/doc_helpers'
12
12
  require 'ecoportal/api/common/logging'
13
+ require 'ecoportal/api/common/elastic_apm_integration'
13
14
  require 'ecoportal/api/common/client'
14
15
  require 'ecoportal/api/common/response'
15
16
  require 'ecoportal/api/common/wrapped_response'
@@ -3,7 +3,9 @@ module Ecoportal
3
3
  module Common
4
4
  class BaseModel
5
5
  class UnlinkedModel < Exception
6
- def initialize (msg = "Something went wrong when linking the document.")
6
+ def initialize (msg = "Something went wrong when linking the document.", from: nil, key: nil)
7
+ msg += " From: #{from}." if from
8
+ msg += " key: #{key}." if key
7
9
  super(msg)
8
10
  end
9
11
  end
@@ -28,7 +30,12 @@ module Ecoportal
28
30
  var = "@#{method}".freeze
29
31
  key = key.to_s.freeze
30
32
  define_method(method) do
31
- return instance_variable_get(var) if instance_variable_defined?(var)
33
+ if instance_variable_defined?(var)
34
+ value = instance_variable_get(var)
35
+ return value unless nullable
36
+ return value if (value && doc[key]) || (!value && !doc[key])
37
+ remove_instance_variable(var)
38
+ end
32
39
  doc[key] ||= {} unless nullable
33
40
  return instance_variable_set(var, nil) unless doc[key]
34
41
 
@@ -53,19 +60,19 @@ module Ecoportal
53
60
  end
54
61
 
55
62
  def doc
56
- raise UnlinkedModel.new unless linked?
63
+ raise UnlinkedModel.new(from: "#{self.class}#doc", key: _key) unless linked?
57
64
  return @doc if is_root?
58
65
  _parent.doc.dig(*[_key].flatten)
59
66
  end
60
67
 
61
68
  def original_doc
62
- raise UnlinkedModel.new unless linked?
69
+ raise UnlinkedModel.new(from: "#{self.class}#original_doc", key: _key) unless linked?
63
70
  return @original_doc if is_root?
64
71
  _parent.original_doc.dig(*[_key].flatten)
65
72
  end
66
73
 
67
74
  def initial_doc
68
- raise UnlinkedModel.new unless linked?
75
+ raise UnlinkedModel.new(from: "#{self.class}#initial_doc", key: _key) unless linked?
69
76
  return @initial_doc if is_root?
70
77
  _parent.initial_doc.dig(*[_key].flatten)
71
78
  end
@@ -78,18 +85,19 @@ module Ecoportal
78
85
  doc.to_json(*args)
79
86
  end
80
87
 
81
- def as_update(ref = :last)
88
+ def as_update(ref = :last, ignore: [])
82
89
  new_doc = as_json
83
90
  ref_doc = ref == :total ? initial_doc : original_doc
84
- Common::HashDiff.diff(new_doc, ref_doc)
91
+ Common::HashDiff.diff(new_doc, ref_doc, ignore: ignore)
85
92
  end
86
93
 
87
94
  def dirty?
88
95
  as_update != {}
89
96
  end
90
97
 
98
+ # It consolidates all the changes carried by `doc` by setting it as `original_doc`.
91
99
  def consolidate!
92
- raise UnlinkedModel.new unless linked?
100
+ raise UnlinkedModel.new(from: "#{self.class}#consolidate!", key: _key) unless linked?
93
101
  new_doc = JSON.parse(doc.to_json)
94
102
  if is_root?
95
103
  @original_doc = new_doc
@@ -98,13 +106,32 @@ module Ecoportal
98
106
  end
99
107
  end
100
108
 
101
- def reset!
102
- raise UnlinkedModel.new unless linked?
103
- new_doc = JSON.parse(original_doc.to_json)
104
- if is_root?
105
- @doc = new_doc
109
+ # It removes all the changes carried by `doc` by restoring `original_doc` into `doc`.
110
+ # @note
111
+ # 1. When there are nullable properties, it may be required to apply `reset!` from the parent
112
+ # i.e. `parent.reset!("child")` # when parent.child is `nil`
113
+ # 2. In such a case, only immediate childs are allowed to be reset
114
+ # @param key [String, Array<String>, nil] if given, it only resets the specified property
115
+ def reset!(key = nil)
116
+ raise "'key' should be a String. Given #{key}" unless !key || key.is_a?(String)
117
+ raise UnlinkedModel.new(from: "#{self.class}#reset!", key: _key) unless linked?
118
+
119
+ if key
120
+ if self.respond_to?(key) && child = self.send(key) && child.is_a?(Ecoportal::API::Common::BaseModel)
121
+ child.reset!
122
+ else
123
+ new_doc = original_doc && original_doc[key]
124
+ dig_set(doc, [key], new_doc && JSON.parse(new_doc.to_json))
125
+ # regenerate object if new_doc is null
126
+ self.send(key) if !new_doc && self.respond_to?(key)
127
+ end
106
128
  else
107
- dig_set(_parent.doc, [_key].flatten, new_doc)
129
+ new_doc = JSON.parse(original_doc.to_json)
130
+ if is_root?
131
+ @doc = new_doc
132
+ else
133
+ dig_set(_parent.doc, [_key].flatten, new_doc)
134
+ end
108
135
  end
109
136
  end
110
137
 
@@ -133,6 +160,17 @@ module Ecoportal
133
160
  end
134
161
  end
135
162
 
163
+ def set_uniq_array_keep_order(key, value)
164
+ unless value.is_a?(Array)
165
+ raise "#{key}= needs to be passed an Array, got #{value.class}"
166
+ end
167
+ ini_vals = (original_doc && original_doc[key]) || []
168
+
169
+ value = value.uniq
170
+ # preserve original order to avoid false updates
171
+ doc[key] = ((ini_vals & value) + (value - ini_vals)).compact
172
+ end
173
+
136
174
  end
137
175
  end
138
176
  end
@@ -10,6 +10,9 @@ module Ecoportal
10
10
  # - to return `HTTP::Response` ([response.rb](https://github.com/httprb/http/blob/master/lib/http/response.rb))
11
11
  # @attr_reader logger [Logger] the logger.
12
12
  class Client
13
+ include Common::ElasticApmIntegration
14
+ DELAY_REQUEST_RETRY = 5
15
+
13
16
  attr_accessor :logger
14
17
 
15
18
  # @note the `api_key` will be automatically added as parameter `X-ApiKey` in the header of the http requests.
@@ -22,6 +25,7 @@ module Ecoportal
22
25
  @version = version
23
26
  @api_key = api_key
24
27
  @logger = logger
28
+ @host = host
25
29
  if host.match(/^localhost|^127\.0\.0\.1/)
26
30
  @base_uri = "http://#{host}/api/"
27
31
  else
@@ -106,7 +110,7 @@ module Ecoportal
106
110
  # basic HTTP connection to the block.
107
111
  # @yield [http] launch specific http request.
108
112
  # @yieldparam http [HTTP] the http connection.
109
- # @yieldreturn [Common::Response] the basic custom reponse object.
113
+ # @yieldreturn [Common::Response] the basic custom response object.
110
114
  # @return [Common::Reponse] the basic custom response object.
111
115
  def request
112
116
  wrap_response yield(base_request)
@@ -151,11 +155,13 @@ module Ecoportal
151
155
 
152
156
  private
153
157
 
154
- def instrument(method, path, data = nil)
158
+ def instrument(method, path, data = nil, &block)
159
+ raise "Expected block" unless block
155
160
  start_time = Time.now.to_f
156
- log(:info) { "#{method} #{url_for(path)}" }
161
+ log(:info) { "#{method} #{url_for(path)}" }
157
162
  log(:debug) { "Data: #{JSON.pretty_generate(data)}" }
158
- yield.tap do |result|
163
+
164
+ with_retry(&block).tap do |result|
159
165
  end_time = Time.now.to_f
160
166
  log(result.success?? :info : :warn) do
161
167
  "Took %.2fs, Status #{result.status}" % (end_time - start_time)
@@ -165,6 +171,19 @@ module Ecoportal
165
171
  end if @response_logging_enabled
166
172
  end
167
173
  end
174
+
175
+ # Helper to ensure unexpected server errors do not bring client scripts immediately down
176
+ def with_retry(attempts = 3, delay = DELAY_REQUEST_RETRY)
177
+ response = nil
178
+ attempts.times do |i|
179
+ response = yield
180
+ return response unless unexpected_server_error?(response.status)
181
+ log_unexpected_server_error(response)
182
+ sleep(delay) if i < attempts
183
+ end
184
+ response
185
+ end
186
+
168
187
  end
169
188
  end
170
189
  end
@@ -0,0 +1,112 @@
1
+ require 'elastic-apm'
2
+ module Ecoportal
3
+ module API
4
+ module Common
5
+ module ElasticApmIntegration
6
+
7
+ class UnexpectedServerError < StandardError
8
+ def initialize(code, msg)
9
+ super("Code: #{code} -- Error: #{msg}")
10
+ end
11
+ end
12
+
13
+ APM_SERVICE_NAME = 'ecoportal-api-gem'
14
+
15
+ # Log only errors that are only server's responsibility
16
+ def log_unexpected_server_error(response)
17
+ raise "Expecting Ecoportal::API::Common::Response. Given: #{response.class}" unless response.is_a?(Common::Response)
18
+ return nil unless elastic_apm_service
19
+ return nil unless unexpected_server_error?(response.status)
20
+ if ElasticAPM.running?
21
+ ElasticAPM.report(UnexpectedServerError.new(response.status, response.body))
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def unexpected_server_error?(code)
28
+ code && (code >= 500) && (code <= 599)
29
+ end
30
+
31
+ # finalizer to stop the agent
32
+ close_elastic_apm = Proc.new do |id|
33
+ begin
34
+ if ElasticAPM.running?
35
+ puts "Stopping ElasticAPM service"
36
+ ElasticAPM.stop
37
+ end
38
+ rescue StandardError => e
39
+ # Silent
40
+ end
41
+ end
42
+ ObjectSpace.define_finalizer("ElasticAPM", close_elastic_apm)
43
+
44
+ def elastic_apm_service
45
+ return false if @disable_apm
46
+ begin
47
+ ElasticAPM.start(**elastic_apm_options) unless ElasticAPM.running?
48
+ rescue StandardError => e
49
+ @disable_apm = true
50
+ puts "ElasticAPM services not available: #{e}"
51
+ end
52
+ end
53
+
54
+ def elastic_apm_options
55
+ {
56
+ service_name: APM_SERVICE_NAME,
57
+ server_url: elastic_apm_url,
58
+ secret_token: elastic_apm_key,
59
+ environment: environment,
60
+ #http_compression: false,
61
+ transaction_sample_rate: 0.1,
62
+ transaction_max_spans: 100,
63
+ span_frames_min_duration: "5ms"
64
+ }.tap do |options|
65
+ options.merge!({
66
+ log_level: Logger::DEBUG,
67
+ log_path: File.join(__dir__, "elastic_apm.log")
68
+ }) if false
69
+ end
70
+ end
71
+
72
+ def elastic_apm_url
73
+ @elastic_apm_url ||= "https://".tap do |url|
74
+ url << "#{elastic_apm_account_id}"
75
+ url << ".#{elastic_apm_base_url}"
76
+ url << ":#{elastic_apm_port}"
77
+ end
78
+ end
79
+
80
+ def elastic_apm_key
81
+ @elastic_apm_key ||= ENV['ELASTIC_APM_KEY']
82
+ end
83
+
84
+ def elastic_apm_account_id
85
+ @elastic_apm_account_id ||= ENV['ELASTIC_APM_ACCOUNT_ID']
86
+ end
87
+
88
+ def elastic_apm_base_url
89
+ @elastic_apm_base_url ||= "apm.#{elastic_apm_region}.aws.cloud.es.io"
90
+ end
91
+
92
+ def elastic_apm_region
93
+ @elastic_apm_region ||= ENV['ELASTIC_APM_REGION'] || "ap-southeast-2"
94
+ end
95
+
96
+
97
+ def elastic_apm_port
98
+ @elastic_apm_port ||= ENV['ELASTIC_APM_PORT'] || "443"
99
+ end
100
+
101
+ def environment
102
+ @environment ||= "unknown".tap do |value|
103
+ if instance_variable_defined?(:@host) && env = @host.gsub(".ecoportal.com", '')
104
+ value.clear << env
105
+ end
106
+ end
107
+ end
108
+
109
+ end
110
+ end
111
+ end
112
+ end
@@ -5,15 +5,16 @@ module Ecoportal
5
5
  ID_KEYS = %w[id]
6
6
 
7
7
  class << self
8
- def diff(a, b)
9
- return a if a.class != b.class
8
+
9
+ def diff(a, b, ignore: [])
10
10
  case a
11
11
  when Hash
12
12
  {}.tap do |diffed|
13
13
  a.each do |key, a_value|
14
- b_value = b[key]
15
- next if a_value == b_value && !ID_KEYS.include?(key)
16
- diffed[key] = diff(a_value, b_value)
14
+ b_value = b && b[key]
15
+ no_changes = (a_value == b_value) || ignore.include?(key)
16
+ next if !ID_KEYS.include?(key) && no_changes
17
+ diffed[key] = diff(a_value, b_value, ignore: ignore)
17
18
  diffed.delete(key) if diffed[key] == {}
18
19
  end
19
20
  # All keys are IDs, so it's actually blank
@@ -22,10 +23,10 @@ module Ecoportal
22
23
  end
23
24
  end
24
25
  when Array
25
- return a unless a.length == b.length
26
+ return a unless b.is_a?(Array) && a.length == b.length
26
27
  a.map.with_index do |a_value, idx|
27
28
  b_value = b[idx]
28
- diff(a_value, b_value)
29
+ diff(a_value, b_value, ignore: ignore)
29
30
  end.reject do |el|
30
31
  el == {}
31
32
  end
@@ -0,0 +1,9 @@
1
+ module Ecoportal
2
+ module API
3
+ module Errors
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'ecoportal/api/errors/base'
9
+ require 'ecoportal/api/errors/time_out'
@@ -0,0 +1,8 @@
1
+ module Ecoportal
2
+ module API
3
+ module Errors
4
+ class Base < StandardError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Ecoportal
2
+ module API
3
+ module Errors
4
+ class TimeOut < Errors::Base
5
+ end
6
+ end
7
+ end
8
+ end
@@ -2,29 +2,26 @@ module Ecoportal
2
2
  module API
3
3
  class Internal
4
4
  class Account < Common::BaseModel
5
- passthrough :policy_group_ids, :landing_page_id, :permissions_preset, :permissions_custom,
6
- :preferences, :prefilter, :login_provider_ids, :starred_ids, :accept_eula,
7
- :send_invites, :default_tag
5
+ PROPERTIES = [
6
+ "user_id", "policy_group_ids", "default_tag", "prefilter",
7
+ "permissions_custom", "permissions_merged", "preferences",
8
+ "login_provider_ids", "starred_ids", "landing_page_id",
9
+ "accept_eula", "send_invites"
10
+ ]
11
+ passthrough *PROPERTIES.map(&:to_sym)
8
12
 
9
13
  class_resolver :preferences_class, "Ecoportal::API::Internal::Preferences"
10
14
  class_resolver :permissions_class, "Ecoportal::API::Internal::Permissions"
11
15
 
12
16
  embeds_one :permissions, key: "permissions_custom", klass: :permissions_class
17
+ embeds_one :perms_merged, key: "permissions_merged", klass: :permissions_class
13
18
  embeds_one :preferences, klass: :preferences_class
14
19
 
15
20
  # Sets the `policy_group_ids`
16
21
  # @note it preserves the original order
17
22
  # @param value [Array<String>] the policy group ids to be set.
18
23
  def policy_group_ids=(value)
19
- unless value.is_a?(Array)
20
- raise "policy_group_ids= needs to be passed an Array, got #{value.class}"
21
- end
22
-
23
- value.uniq!
24
- ini_ids = (original_doc && original_doc["policy_group_ids"]) || []
25
- # preserve original order to avoid false updates
26
- doc["policy_group_ids"] = (ini_ids & value) + (value - ini_ids)
27
- doc["policy_group_ids"].compact
24
+ set_uniq_array_keep_order("policy_group_ids", value)
28
25
  end
29
26
 
30
27
  # @return [Array<String>] the policy group ids of this user.
@@ -34,10 +31,7 @@ module Ecoportal
34
31
 
35
32
  # Sets the `login_provider_ids`
36
33
  def login_provider_ids=(value)
37
- unless value.is_a?(Array)
38
- raise "login_provider_ids= needs to be passed an Array, got #{value.class}"
39
- end
40
- doc["login_provider_ids"] = value.compact
34
+ set_uniq_array_keep_order("login_provider_ids", value)
41
35
  end
42
36
 
43
37
  # @return [Array<String>] the login provider ids of this user.
@@ -47,10 +41,7 @@ module Ecoportal
47
41
 
48
42
  # Sets the `starred_ids`
49
43
  def starred_ids=(value)
50
- unless value.is_a?(Array)
51
- raise "starred_ids= needs to be passed an Array, got #{value.class}"
52
- end
53
- doc["starred_ids"] = value.compact
44
+ set_uniq_array_keep_order("starred_ids", value)
54
45
  end
55
46
 
56
47
  # @return [Array<String>] the starred page ids of this user.
@@ -58,20 +49,6 @@ module Ecoportal
58
49
  doc["starred_ids"] ||= []
59
50
  end
60
51
 
61
- # Sets the `permissions_preset`.
62
- # @note basically the same as `permissions_preset=` but when `"custom"`, it's changed to `nil`
63
- # @param value [nil, String] preset name.
64
- def preset=(value)
65
- self.permissions_preset = value == "custom" ? nil : value
66
- end
67
-
68
- # Gets the `permissions_preset`.
69
- # @note basically the same as `permissions_preset` but when 'nil', it returns `"custom"` instead
70
- # @return [nil, String] preset name.
71
- def preset
72
- self.permissions_preset.nil? ? "custom" : self.permissions_preset
73
- end
74
-
75
52
  # It preserves the values of keys that are not defined in `value`.
76
53
  # @param value [Hash] the abilities that you want to update.
77
54
  def permissions_custom=(value)
@@ -88,15 +65,16 @@ module Ecoportal
88
65
 
89
66
  def as_json
90
67
  super.tap do |hash|
91
- if preset == "custom"
92
- hash["permissions_custom"] = permissions.as_json
93
- else
94
- hash.delete "permissions_custom"
95
- end
68
+ hash["permissions_custom"] = permissions.as_json
69
+ hash["permissions_merged"] = perms_merged.as_json
96
70
  hash["preferences"] = preferences.as_json
97
71
  end
98
72
  end
99
73
 
74
+ def as_update(ref = :last, ignore: [])
75
+ super(ref, ignore: ignore | ["user_id", "permissions_merged", "prefilter"])
76
+ end
77
+
100
78
  end
101
79
  end
102
80
  end
@@ -3,9 +3,9 @@ module Ecoportal
3
3
  class Internal
4
4
  class Permissions < Common::BaseModel
5
5
  passthrough :files, :data, :reports
6
- passthrough :organization, :person_core, :person_core_create, :person_core_edit
7
- passthrough :person_account, :person_details
8
- passthrough :pages, :page_editor, :registers, :tasks
6
+ passthrough :organization, :pages, :page_editor, :registers, :tasks
7
+ passthrough :person_core, :person_core_create, :person_core_edit
8
+ passthrough :person_details, :person_account, :person_abilities
9
9
  end
10
10
  end
11
11
  end
@@ -11,10 +11,14 @@ module Ecoportal
11
11
  super.update("account" => account&.as_json)
12
12
  end
13
13
 
14
+ def as_update(ref = :last, ignore: [])
15
+ super(ref, ignore: ignore | ["user_id", "permissions_merged", "prefilter"])
16
+ end
17
+
14
18
  # Sets the Account to the person, depending on the paramter received:
15
19
  # - `nil`: blanks the account.
16
20
  # - `Account`: sets a copy of the object param as account.
17
- # - `Hash`: slices the properties of `Account`.
21
+ # - `Hash`: slices the properties of `Account` (keeping the value of `user_id` if there was already account).
18
22
  # @note this method does not make dirty the account (meaning that `as_json` will be an empty hash `{}`)
19
23
  # @param value [nil, Account, Hash] value to be set.
20
24
  # @return [nil, Account] the resulting `Account` set to the person.
@@ -25,7 +29,9 @@ module Ecoportal
25
29
  when Internal::Account
26
30
  doc["account"] = JSON.parse(value.to_json)
27
31
  when Hash
28
- doc["account"] = value.slice(*%w[policy_group_ids default_tag landing_page_id permissions_preset permissions_custom preferences prefilter login_provider_ids starred_ids])
32
+ user_id = account.user_id if account
33
+ doc["account"] = value.slice(*Internal::Account::PROPERTIES)
34
+ doc["account"]["user_id"] = user_id if user_id
29
35
  else
30
36
  # TODO
31
37
  raise "Invalid set on account: Need nil, Account or Hash; got #{value.class}"
@@ -37,7 +37,7 @@ module Ecoportal
37
37
  loop do
38
38
  params.update(cursor_id: cursor_id) if cursor_id
39
39
  response = client.get("/people", params: params)
40
- body = body_data(response.body)
40
+ body = response && body_data(response.body)
41
41
  raise "Request failed - Status #{response.status}: #{body}" unless response.success?
42
42
 
43
43
  unless silent || (total = body["total_results"]) == 0
@@ -139,7 +139,8 @@ module Ecoportal
139
139
  if status&.complete?
140
140
  operation.process_response job_result(job_id, operation)
141
141
  else
142
- raise "Job `#{job_id}` not complete. Probably timeout after #{JOB_TIMEOUT} seconds. Current status: #{status}"
142
+ msg = "Job `#{job_id}` not complete. Probably timeout after #{JOB_TIMEOUT} seconds. Current status: #{status}"
143
+ raise API::Errors::TimeOut.new msg
143
144
  end
144
145
  end
145
146
 
@@ -154,8 +155,8 @@ module Ecoportal
154
155
  JobStatus = Struct.new(:id, :complete?, :errored?, :progress)
155
156
  def job_status(job_id)
156
157
  response = client.get("/people/job/#{CGI.escape(job_id)}/status")
157
- body = body_data(response.body)
158
- raise "Status error" unless response.success?
158
+ body = response && body_data(response.body)
159
+ raise "Status error (#{response.status}) - Errors: #{body}" unless response.success?
159
160
  JobStatus.new(
160
161
  body["id"],
161
162
  body["complete"],
@@ -190,8 +191,9 @@ module Ecoportal
190
191
  job_id = nil
191
192
  client.without_response_logging do
192
193
  client.post("/people/job", data: operation.as_json).tap do |response|
193
- job_id = body_data(response.body)["id"]
194
+ job_id = body_data(response.body)["id"] if response.success?
194
195
  end
196
+ raise "Could not create job - Error (#{response.status}): #{body_data(response.body)}" unless job_id
195
197
  end
196
198
  job_id
197
199
  end
@@ -64,12 +64,8 @@ module Ecoportal
64
64
  raise "Invalid filter tag #{tag.inspect}"
65
65
  end
66
66
  tag.upcase
67
- end.uniq
68
-
69
- ini_tags = (original_doc && original_doc["filter_tags"]) || []
70
- # preserve original order to avoid false updates
71
- doc["filter_tags"] = (ini_tags & end_tags) + (end_tags - ini_tags)
72
- doc["filter_tags"].compact
67
+ end
68
+ set_uniq_array_keep_order("filter_tags", end_tags)
73
69
  end
74
70
 
75
71
  # @return [Array<String>] the filter tags of this person.
@@ -81,6 +77,10 @@ module Ecoportal
81
77
  super.merge "details" => details&.as_json
82
78
  end
83
79
 
80
+ def as_update(ref = :last, ignore: [])
81
+ super(ignore: ignore | ["subordinates"])
82
+ end
83
+
84
84
  # Sets the PersonDetails to the person, depending on the paramter received:
85
85
  # - `nil`: blanks the details.
86
86
  # - `PersonDetails`: sets a copy of the object param as details.
@@ -6,25 +6,24 @@ module Ecoportal
6
6
  passthrough :id, :alt_id, :type, :name, :shared, :multiple
7
7
 
8
8
  def value
9
- return @value if defined?(@value)
10
- @value = case type
11
- when "text", "phone_number", "number", "boolean", "select"
12
- doc["value"]
13
- when "date"
14
- if doc["value"]
15
- maybe_multiple(doc["value"]) do |v|
16
- Date.iso8601(v)
17
- end
18
- end
19
- else
20
- raise "Unknown type #{type}"
21
- end
9
+ case type
10
+ when "text", "phone_number", "number", "boolean", "select"
11
+ doc["value"]
12
+ when "date"
13
+ if doc["value"]
14
+ maybe_multiple(doc["value"]) do |v|
15
+ Date.iso8601(v)
16
+ end
17
+ end
18
+ else
19
+ raise "Unknown type #{type}"
20
+ end
22
21
  end
23
22
 
24
23
  def value=(value)
25
24
  case type
26
25
  when "text", "phone_number", "select"
27
- doc["value"] = @value = maybe_multiple(value) do |v|
26
+ doc["value"] = maybe_multiple(value) do |v|
28
27
  v&.to_s
29
28
  end
30
29
  when "number"
@@ -33,23 +32,21 @@ module Ecoportal
33
32
  raise "Invalid number type #{v.class}"
34
33
  end
35
34
  end
36
- doc["value"] = @value = value
35
+ doc["value"] = value
37
36
  when "boolean"
38
- doc["value"] = @value = !!value
37
+ doc["value"] = !!value
39
38
  when "date"
40
39
  maybe_multiple(value) do |v|
41
40
  unless v.nil? || v.respond_to?(:to_date)
42
41
  raise "Invalid date type #{v.class}"
43
42
  end
44
43
  end
45
- @value = value
46
- doc["value"] = maybe_multiple(@value) do |v|
44
+ doc["value"] = maybe_multiple(value) do |v|
47
45
  v&.to_date&.to_s
48
46
  end
49
47
  else
50
48
  raise "Unknown type #{type}"
51
49
  end
52
- @value
53
50
  end
54
51
 
55
52
  def maybe_multiple(value)
@@ -1,5 +1,5 @@
1
1
  module Ecoportal
2
2
  module API
3
- VERSION = "0.8.2"
3
+ VERSION = "0.8.3"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecoportal-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tapio Saarinen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-02-23 00:00:00.000000000 Z
11
+ date: 2021-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.2.11
19
+ version: 2.2.17
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '2.3'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 2.2.11
29
+ version: 2.2.17
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2.3'
@@ -144,6 +144,46 @@ dependencies:
144
144
  - - "<"
145
145
  - !ruby/object:Gem::Version
146
146
  version: '5'
147
+ - !ruby/object:Gem::Dependency
148
+ name: dotenv
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: 2.7.6
154
+ - - "<"
155
+ - !ruby/object:Gem::Version
156
+ version: '2.8'
157
+ type: :runtime
158
+ prerelease: false
159
+ version_requirements: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 2.7.6
164
+ - - "<"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.8'
167
+ - !ruby/object:Gem::Dependency
168
+ name: elastic-apm
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: 4.0.0
174
+ - - "<"
175
+ - !ruby/object:Gem::Version
176
+ version: '4.1'
177
+ type: :runtime
178
+ prerelease: false
179
+ version_requirements: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: 4.0.0
184
+ - - "<"
185
+ - !ruby/object:Gem::Version
186
+ version: '4.1'
147
187
  - !ruby/object:Gem::Dependency
148
188
  name: hash-polyfill
149
189
  requirement: !ruby/object:Gem::Requirement
@@ -189,10 +229,14 @@ files:
189
229
  - lib/ecoportal/api/common/batch_response.rb
190
230
  - lib/ecoportal/api/common/client.rb
191
231
  - lib/ecoportal/api/common/doc_helpers.rb
232
+ - lib/ecoportal/api/common/elastic_apm_integration.rb
192
233
  - lib/ecoportal/api/common/hash_diff.rb
193
234
  - lib/ecoportal/api/common/logging.rb
194
235
  - lib/ecoportal/api/common/response.rb
195
236
  - lib/ecoportal/api/common/wrapped_response.rb
237
+ - lib/ecoportal/api/errors.rb
238
+ - lib/ecoportal/api/errors/base.rb
239
+ - lib/ecoportal/api/errors/time_out.rb
196
240
  - lib/ecoportal/api/internal.rb
197
241
  - lib/ecoportal/api/internal/account.rb
198
242
  - lib/ecoportal/api/internal/login_provider.rb