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 +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
|