ecoportal-api 0.7.4 → 0.8.4
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 +99 -0
- data/ecoportal-api.gemspec +11 -7
- data/lib/ecoportal/api/common/base_model.rb +56 -18
- data/lib/ecoportal/api/common/batch_operation.rb +7 -1
- data/lib/ecoportal/api/common/batch_response.rb +2 -2
- data/lib/ecoportal/api/common/client.rb +40 -15
- 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/common/response.rb +1 -1
- data/lib/ecoportal/api/common/wrapped_response.rb +2 -2
- data/lib/ecoportal/api/common.rb +1 -0
- data/lib/ecoportal/api/errors/base.rb +8 -0
- data/lib/ecoportal/api/errors/time_out.rb +8 -0
- data/lib/ecoportal/api/errors.rb +9 -0
- data/lib/ecoportal/api/internal/account.rb +17 -39
- data/lib/ecoportal/api/internal/permissions.rb +4 -3
- data/lib/ecoportal/api/internal/person.rb +8 -2
- data/lib/ecoportal/api/v1/people.rb +48 -31
- data/lib/ecoportal/api/v1/person.rb +7 -7
- data/lib/ecoportal/api/v1/schema_field_value.rb +16 -19
- data/lib/ecoportal/api/version.rb +1 -1
- data/lib/ecoportal/api.rb +2 -1
- metadata +99 -50
- data/lib/ecoportal/api/v2/field.rb +0 -9
- data/lib/ecoportal/api/v2/page.rb +0 -30
- data/lib/ecoportal/api/v2/pages.rb +0 -46
- data/lib/ecoportal/api/v2/register_search_result.rb +0 -20
- data/lib/ecoportal/api/v2/registers.rb +0 -65
- data/lib/ecoportal/api/v2/stage.rb +0 -9
- data/lib/ecoportal/api/v2.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc651f2474f74b8f767c9df5b4611bd8adefdff75f8e25365135c89561229738
|
4
|
+
data.tar.gz: cb7a8ff5c0c03ba7e371f987d6b277c001b33e57716ab6cf03628d28bac53b61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f89285565edeaa9cbe4a98970bae8602a943c7c865121a89782f512353a8be74529578eecb4f48e893f546f172425dd38af0759412f53b8fc3a91989d6dbe52a
|
7
|
+
data.tar.gz: 4f51e3e6d080745261c033acf511fdd341ebf9f97003f4dda5067c49c9ef51adf966c51cf268e4991e741b45c803fc6c1e821051832c090ba2c685f03c4eaa7d
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,101 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## [0.8.4] - 2021-10-xx
|
5
|
+
|
6
|
+
### Added
|
7
|
+
- `Ecoportal::API::Internal::Permissions` added abilities
|
8
|
+
- `visitor_management`, `cross_register_reporting` and `broadcast_notifications`
|
9
|
+
- Some yardocs too
|
10
|
+
- Some callbacks are done in a non-obvious way and the returned object type was not documented
|
11
|
+
- For this reason, some yardocs have been added to some of the parts that have been worked on.
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
- `Ecoportal::API::V1::People#create_job`
|
15
|
+
- **Removed** call to `BatchOperation#process_response`
|
16
|
+
- The method was is already called by `job_result`
|
17
|
+
- As a consequence there was a double up of `callbacks`
|
18
|
+
- **Fixed** line in wrong position
|
19
|
+
- `Ecoportal::API::V1::People#batch`
|
20
|
+
- `Ecoportal::API::Common::ElasticApmIntegration#unexpected_server_error?`
|
21
|
+
- No code or code lesser than 100 is a server error as well
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
- `Ecoportal::API::Common::Client`: changed
|
25
|
+
- Logging the **response** of batches or batch jobs can be handy when debugging the back-end
|
26
|
+
- **removed** method `#without_response_logging`
|
27
|
+
- This change entailed to remove dependencies in `Ecoportal::API::V1::People`
|
28
|
+
- Specifically in methods `#batch`, `#job_result` and `#create_job`
|
29
|
+
- `@response_logging_enabled` to be set in initialization stage (added parameter for `.new`)
|
30
|
+
|
31
|
+
## [0.8.3] - 2021-05-24
|
32
|
+
|
33
|
+
### Added
|
34
|
+
- `Ecoportal::API::Errors` namespace
|
35
|
+
- `Ecoportal::API::Errors::Base` base error class.
|
36
|
+
- `Ecoportal::API::Errors::TimeOut` error when an api request fails with time out.
|
37
|
+
- This serves the purpose to allow a client script to re-start the process where it stopped by capturing this specific Error
|
38
|
+
- `Ecoportal::API::Common::BaseModel::UnlinkedModel` added more description to track down the source of the error.
|
39
|
+
- `Ecoportal::API::Common::BaseModel#reset!` added parameter `key`, which should try to recover `doc[key]` from `original_doc[key]`
|
40
|
+
- Thanks to this, you are supposed to be able to do things like:
|
41
|
+
- `person.account = nil && person.reset!("account")`
|
42
|
+
- `person.name = nil && person.reset!("name")`
|
43
|
+
- `Ecoportal::API::V1::People#job` methods to provide more information on failure.
|
44
|
+
- Specific changes due to eP **release `1.5.9.70`** (_Policy Group Abilities_)
|
45
|
+
- `Ecoportal::API::Internal::Account#permissions_merged`
|
46
|
+
- `Ecoportal::API::Internal::Permissions#person_abilities` (new ability)
|
47
|
+
- `Ecoportal::API::Internal::Account#user_id`
|
48
|
+
|
49
|
+
### Fixed
|
50
|
+
- `Ecoportal::API::Internal::Account`: consistency in setting arrays (`uniq!` & `compact`)
|
51
|
+
- `#policy_group_ids=`, `#login_provider_ids=`, `#starred_ids=`
|
52
|
+
- `Ecoportal::API::Common::HashDiff.diff` was including empty `{}` objects
|
53
|
+
- This change sacrifices the case `account: {}` (which will be also removed from `as_update`), but it should be fine.
|
54
|
+
|
55
|
+
### Changed
|
56
|
+
- `Ecoportal::API::V1::People#job` to raise specific error on time out `API::Errors::TimeOut`
|
57
|
+
- Specific changes due to eP **release `1.5.9.70`** (_Policy Group Abilities_)
|
58
|
+
- `Ecoportal::API::Internal::Account` **removed** methods: `#permissions_preset`, `#preset` and `#preset=`
|
59
|
+
- `Ecoportal::API::Internal::Person#account=` added support for `user_id` which should remain unchanged when existing
|
60
|
+
- **remove** from `as_update` **read-only** data
|
61
|
+
- `Ecoportal::API::Common::HashDiff.diff` added parameter `:ignore` (`Array`)
|
62
|
+
- `Ecoportal::API::Common::BaseModel#as_update` added parameter `:ignore`
|
63
|
+
- `Ecoportal::API::V1::Person#as_update` added method, which ignores `subordinates`
|
64
|
+
- `Ecoportal::API::Internal::Person#as_update` added method, which ignores `user_id`, `permissions_merged` and `prefilter`
|
65
|
+
- `Ecoportal::API::Internal::Account#as_update` added method, which ignores `user_id`, `permissions_merged` and `prefilter`
|
66
|
+
- `Ecoportal::API::Common::Client` native support for `elastic-apm`
|
67
|
+
- Via new module `Ecoportal::API::Common::ElasticApmIntegration` with method `log_unexpected_server_error`, which will only log an `UnexpectedServerError` to _ElasticAPM_ if
|
68
|
+
1. There's a correct configuration: environmental variables `ELASTIC_APM_KEY` and `ELASTIC_APM_ACCOUNT_ID` are defined
|
69
|
+
2. The `Response` from the server gave `code` in the range `5xx` (which are those under server responsibility)
|
70
|
+
- `Ecoportal::API::Common::Client` added retry logics when `response.status == 5xx`
|
71
|
+
|
72
|
+
## [0.8.2] - 2021-02-24
|
73
|
+
|
74
|
+
### Added
|
75
|
+
|
76
|
+
### Fixed
|
77
|
+
- `Ecoportal::API::V1::Person#filter_tags=` should ignore `nil` values
|
78
|
+
|
79
|
+
### Changed
|
80
|
+
- removed all the namespace under `Ecoportal::API::V2` as that is managed by `ecoportal-api-oozes` gem
|
81
|
+
- url: https://rubygems.org/gems/ecoportal-api-oozes
|
82
|
+
- `Ecoportal::API::V1::People.get` should return a `Person` object
|
83
|
+
- observe that it was returning the `WrappedResponse` (an `Enumerable` helper that works better when getting multiple people).
|
84
|
+
|
85
|
+
## [0.7.5] - 2021-02-12
|
86
|
+
|
87
|
+
### Added
|
88
|
+
|
89
|
+
### Fixed
|
90
|
+
- `pretty_print` method was colliding with `pp` module:
|
91
|
+
- renamed to `Ecoportal::API::Common::BaseModel#print_pretty`
|
92
|
+
- renamed to `Ecoportal::API::Common::BatchReponse#print_pretty`
|
93
|
+
- renamed to `Ecoportal::API::Common::Reponse#print_pretty`
|
94
|
+
- renamed to `Ecoportal::API::Common::WrappedResponse#print_pretty`
|
95
|
+
|
96
|
+
### Changed
|
97
|
+
- forgot to change `Ecoportal::API::VERSION` during last release
|
98
|
+
|
4
99
|
## [0.7.4] - 2021-01-2
|
5
100
|
|
6
101
|
### Added
|
@@ -62,6 +157,10 @@ All notable changes to this project will be documented in this file.
|
|
62
157
|
## [0.7.0] - 2020-09-11
|
63
158
|
|
64
159
|
### Added
|
160
|
+
- added hook, **private** method `body_data` for child classes to define behaviour on `response.body` to
|
161
|
+
- `Ecoportal::API::V1::People`
|
162
|
+
- `Ecoportal::API::Common::BatchOperation`
|
163
|
+
|
65
164
|
### Changed
|
66
165
|
- `Ecoportal::API::Internal::Permissions`: **update for new abilities of ecoPortal release `1.5.2`**
|
67
166
|
- decoupled abilities: `person_core` into `person_core_create`, `person_core_edit`
|
data/ecoportal-api.gemspec
CHANGED
@@ -13,6 +13,8 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://www.ecoportal.com"
|
14
14
|
spec.licenses = %w[MIT]
|
15
15
|
|
16
|
+
spec.required_ruby_version = '>= 2.4.4'
|
17
|
+
|
16
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
19
|
f.match(%r{^(test|spec|features)/})
|
18
20
|
end
|
@@ -20,13 +22,15 @@ Gem::Specification.new do |spec|
|
|
20
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
23
|
spec.require_paths = ["lib"]
|
22
24
|
|
23
|
-
spec.add_development_dependency "bundler", "
|
24
|
-
spec.add_development_dependency "
|
25
|
-
spec.add_development_dependency "
|
26
|
-
spec.add_development_dependency "yard", "
|
27
|
-
spec.add_development_dependency "redcarpet", "
|
28
|
-
spec.add_development_dependency "pry" , "~> 0.
|
25
|
+
spec.add_development_dependency "bundler", ">= 2.2.17", "< 2.3"
|
26
|
+
spec.add_development_dependency "rspec", ">= 3.10.0", "< 3.11"
|
27
|
+
spec.add_development_dependency "rake", ">= 13.0.3", "< 13.1"
|
28
|
+
spec.add_development_dependency "yard", ">= 0.9.26", "< 0.10"
|
29
|
+
spec.add_development_dependency "redcarpet", ">= 3.5.1", "< 3.6"
|
30
|
+
spec.add_development_dependency "pry" , "~> 0.14"
|
29
31
|
|
30
|
-
spec.add_dependency 'http', '~> 4'
|
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"
|
31
35
|
spec.add_dependency 'hash-polyfill', '~> 0'
|
32
36
|
end
|
@@ -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,20 +106,39 @@ 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
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
138
|
+
def print_pretty
|
139
|
+
puts JSON.pretty_generate(as_json)
|
140
|
+
self
|
141
|
+
end
|
115
142
|
|
116
143
|
protected
|
117
144
|
|
@@ -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
|
@@ -27,7 +27,7 @@ module Ecoportal
|
|
27
27
|
|
28
28
|
log(:info) { "Processing batch responses" }
|
29
29
|
|
30
|
-
response.body.each.with_index do |subresponse, idx|
|
30
|
+
body_data(response.body).each.with_index do |subresponse, idx|
|
31
31
|
callback = @operations[idx][:callback]
|
32
32
|
status = subresponse["status"]
|
33
33
|
body = subresponse["response"]
|
@@ -97,6 +97,12 @@ module Ecoportal
|
|
97
97
|
|
98
98
|
private
|
99
99
|
|
100
|
+
# Hook for other api versions to obtain the raw data of a response
|
101
|
+
# @note this was introduced to allow `v2` to reuse this class
|
102
|
+
def body_data(body)
|
103
|
+
body
|
104
|
+
end
|
105
|
+
|
100
106
|
def log_batch_response(operation, response)
|
101
107
|
level = response.success?? :debug : :warn
|
102
108
|
log(:info) { "BATCH #{operation[:method]} #{operation[:path]}" }
|
@@ -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.
|
@@ -17,11 +20,14 @@ module Ecoportal
|
|
17
20
|
# @param version [String] it is part of the base url and will determine the api version we query against.
|
18
21
|
# @param host [String] api server domain.
|
19
22
|
# @param logger [Logger] an object with `Logger` interface to generate logs.
|
23
|
+
# @param response_logging [Boolean] whether or not batch responses should be logged
|
20
24
|
# @return [Client] an object that holds the configuration of the api connection.
|
21
|
-
def initialize(api_key:, version: "v1", host: "live.ecoportal.com", logger: nil)
|
25
|
+
def initialize(api_key:, version: "v1", host: "live.ecoportal.com", logger: nil, response_logging: false)
|
22
26
|
@version = version
|
23
27
|
@api_key = api_key
|
24
28
|
@logger = logger
|
29
|
+
@host = host
|
30
|
+
@response_logging_enabled = response_logging
|
25
31
|
if host.match(/^localhost|^127\.0\.0\.1/)
|
26
32
|
@base_uri = "http://#{host}/api/"
|
27
33
|
else
|
@@ -31,7 +37,6 @@ module Ecoportal
|
|
31
37
|
if @api_key.nil? || @api_key.match(/\A\W*\z/)
|
32
38
|
log(:error) { "Api-key missing!" }
|
33
39
|
end
|
34
|
-
@response_logging_enabled = true
|
35
40
|
end
|
36
41
|
|
37
42
|
# Logger interface.
|
@@ -106,7 +111,7 @@ module Ecoportal
|
|
106
111
|
# basic HTTP connection to the block.
|
107
112
|
# @yield [http] launch specific http request.
|
108
113
|
# @yieldparam http [HTTP] the http connection.
|
109
|
-
# @yieldreturn [Common::Response] the basic custom
|
114
|
+
# @yieldreturn [Common::Response] the basic custom response object.
|
110
115
|
# @return [Common::Reponse] the basic custom response object.
|
111
116
|
def request
|
112
117
|
wrap_response yield(base_request)
|
@@ -140,22 +145,15 @@ module Ecoportal
|
|
140
145
|
@base_uri+@version+path
|
141
146
|
end
|
142
147
|
|
143
|
-
def without_response_logging(&block)
|
144
|
-
begin
|
145
|
-
@response_logging_enabled = false
|
146
|
-
yield self
|
147
|
-
ensure
|
148
|
-
@response_logging_enabled = true
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
148
|
private
|
153
149
|
|
154
|
-
def instrument(method, path, data = nil)
|
150
|
+
def instrument(method, path, data = nil, &block)
|
151
|
+
raise "Expected block" unless block
|
155
152
|
start_time = Time.now.to_f
|
156
|
-
log(:info)
|
153
|
+
log(:info) { "#{method} #{url_for(path)}" }
|
157
154
|
log(:debug) { "Data: #{JSON.pretty_generate(data)}" }
|
158
|
-
|
155
|
+
|
156
|
+
with_retry(&block).tap do |result|
|
159
157
|
end_time = Time.now.to_f
|
160
158
|
log(result.success?? :info : :warn) do
|
161
159
|
"Took %.2fs, Status #{result.status}" % (end_time - start_time)
|
@@ -165,6 +163,33 @@ module Ecoportal
|
|
165
163
|
end if @response_logging_enabled
|
166
164
|
end
|
167
165
|
end
|
166
|
+
|
167
|
+
# Helper to ensure unexpected server errors do not bring client scripts immediately down
|
168
|
+
def with_retry(attempts = 3, delay = DELAY_REQUEST_RETRY, error_safe: true, &block)
|
169
|
+
response = nil
|
170
|
+
attempts.times do |i|
|
171
|
+
remaining = attempts - i - 1
|
172
|
+
begin
|
173
|
+
response = block.call
|
174
|
+
rescue HTTP::ConnectionError => e
|
175
|
+
raise unless error_safe && remaining > 0
|
176
|
+
log(:error) { "Got connection error: #{e.message}" }
|
177
|
+
response = with_retry(remaining, error_safe: error_safe, &block)
|
178
|
+
rescue IOError => e
|
179
|
+
raise unless error_safe && remaining > 0
|
180
|
+
log(:error) { "Got IO error: #{e.message}" }
|
181
|
+
response = with_retry(remaining, error_safe: error_safe, &block)
|
182
|
+
end
|
183
|
+
return response unless unexpected_server_error?(response.status)
|
184
|
+
log_unexpected_server_error(response)
|
185
|
+
msg = "Got server error (#{response.status}): #{response.body}\n"
|
186
|
+
msg += "Going to retry (#{i} out of #{attempts})"
|
187
|
+
log(:error) { msg }
|
188
|
+
sleep(delay) if i < attempts
|
189
|
+
end
|
190
|
+
response
|
191
|
+
end
|
192
|
+
|
168
193
|
end
|
169
194
|
end
|
170
195
|
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)) || (code <= 99)
|
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
|
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'
|