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 +4 -4
- data/CHANGELOG.md +43 -1
- data/ecoportal-api.gemspec +3 -1
- data/lib/ecoportal/api.rb +2 -0
- data/lib/ecoportal/api/common.rb +1 -0
- data/lib/ecoportal/api/common/base_model.rb +52 -14
- data/lib/ecoportal/api/common/client.rb +23 -4
- data/lib/ecoportal/api/common/elastic_apm_integration.rb +112 -0
- data/lib/ecoportal/api/common/hash_diff.rb +8 -7
- data/lib/ecoportal/api/errors.rb +9 -0
- data/lib/ecoportal/api/errors/base.rb +8 -0
- data/lib/ecoportal/api/errors/time_out.rb +8 -0
- data/lib/ecoportal/api/internal/account.rb +17 -39
- data/lib/ecoportal/api/internal/permissions.rb +3 -3
- data/lib/ecoportal/api/internal/person.rb +8 -2
- data/lib/ecoportal/api/v1/people.rb +7 -5
- data/lib/ecoportal/api/v1/person.rb +6 -6
- data/lib/ecoportal/api/v1/schema_field_value.rb +16 -19
- data/lib/ecoportal/api/version.rb +1 -1
- metadata +48 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a66f25a41e7da2e5d0d38efe75ef485e8c4e830a7af80d458170e1f9e44352a9
|
4
|
+
data.tar.gz: a3b1060723814238dc9b4bbfaa4098b0ae8d8f17320ba4dc9b9c3e33a0a9095c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
|
data/ecoportal-api.gemspec
CHANGED
@@ -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.
|
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"
|
data/lib/ecoportal/api/common.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
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
|
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)
|
161
|
+
log(:info) { "#{method} #{url_for(path)}" }
|
157
162
|
log(:debug) { "Data: #{JSON.pretty_generate(data)}" }
|
158
|
-
|
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
|
-
|
9
|
-
|
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
|
15
|
-
|
16
|
-
|
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
|
@@ -2,29 +2,26 @@ module Ecoportal
|
|
2
2
|
module API
|
3
3
|
class Internal
|
4
4
|
class Account < Common::BaseModel
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
92
|
-
|
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, :
|
7
|
-
passthrough :
|
8
|
-
passthrough :
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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"] =
|
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"] =
|
35
|
+
doc["value"] = value
|
37
36
|
when "boolean"
|
38
|
-
doc["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
|
-
|
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)
|
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.
|
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-
|
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.
|
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.
|
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
|