ecoportal-api 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
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