ecoportal-api 0.7.4 → 0.8.4
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|