iron_bank 5.4.0 → 5.4.1
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/Gemfile.lock +14 -3
- data/Rakefile +8 -0
- data/lib/iron_bank/describe/excluded_fields/deduce_from_query.rb +72 -0
- data/lib/iron_bank/describe/excluded_fields/extract_from_message.rb +56 -0
- data/lib/iron_bank/describe/excluded_fields.rb +15 -58
- data/lib/iron_bank/schema.rb +8 -0
- data/lib/iron_bank/version.rb +1 -1
- data/lib/iron_bank.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5f447a2b12293785811cd3e1f6d170e4ff3186b7de4ce4cac6d12b4322171a4
|
4
|
+
data.tar.gz: '068e99b0fc9fb71eaa3adfa252bf455d2622579feaa6ee1893922d7667130e01'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1daac2047a0798f1471aeeb5872830f413b00135b25fd88f325ffe6b882d192cd9ab30df2bfe2971b3fd8409283935ff5075da6712bad2dd6b79f2c7bc6a6a34
|
7
|
+
data.tar.gz: b09ddb6a6873b5564ba6f552a24228e2e8cb951479125e6605580db0ed1780d8370e5b215084a5217a8f481ddc8fc92a7ea59a262b00ff71252948d3bbeb4d23
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
iron_bank (5.4.
|
4
|
+
iron_bank (5.4.1)
|
5
5
|
faraday (~> 1)
|
6
6
|
faraday_middleware (~> 1)
|
7
7
|
nokogiri (~> 1)
|
@@ -9,19 +9,29 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
-
activesupport (7.
|
12
|
+
activesupport (7.1.1)
|
13
|
+
base64
|
14
|
+
bigdecimal
|
13
15
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
16
|
+
connection_pool (>= 2.2.5)
|
17
|
+
drb
|
14
18
|
i18n (>= 1.6, < 2)
|
15
19
|
minitest (>= 5.1)
|
20
|
+
mutex_m
|
16
21
|
tzinfo (~> 2.0)
|
17
22
|
ast (2.4.2)
|
23
|
+
base64 (0.2.0)
|
24
|
+
bigdecimal (3.1.4)
|
18
25
|
bump (0.10.0)
|
19
26
|
byebug (11.1.3)
|
20
27
|
coderay (1.1.3)
|
21
28
|
concurrent-ruby (1.2.2)
|
29
|
+
connection_pool (2.4.1)
|
22
30
|
diff-lcs (1.5.0)
|
23
31
|
docile (1.4.0)
|
24
32
|
dotenv (2.8.1)
|
33
|
+
drb (2.2.0)
|
34
|
+
ruby2_keywords
|
25
35
|
factory_bot (6.2.1)
|
26
36
|
activesupport (>= 5.0.0)
|
27
37
|
faraday (1.10.3)
|
@@ -57,6 +67,7 @@ GEM
|
|
57
67
|
mini_portile2 (2.8.2)
|
58
68
|
minitest (5.18.0)
|
59
69
|
multipart-post (2.3.0)
|
70
|
+
mutex_m (0.2.0)
|
60
71
|
nokogiri (1.14.3)
|
61
72
|
mini_portile2 (~> 2.8.0)
|
62
73
|
racc (~> 1.4)
|
@@ -141,4 +152,4 @@ DEPENDENCIES
|
|
141
152
|
timecop (~> 0.9)
|
142
153
|
|
143
154
|
BUNDLED WITH
|
144
|
-
2.
|
155
|
+
2.4.19
|
data/Rakefile
CHANGED
@@ -31,6 +31,14 @@ task :excluded_fields, [:filename] do |_t, args|
|
|
31
31
|
setup_iron_bank
|
32
32
|
|
33
33
|
destination = args[:filename] || IronBank.configuration.excluded_fields_file
|
34
|
+
# NOTE: In some instances the error message from Zuora will not include the
|
35
|
+
# unqueryable fields that need to be excluded. When that happens IronBank's
|
36
|
+
# strategy will be to perform a binary search through the fields listed in the
|
37
|
+
# query -- at the cost of performance due to repeated requests sent to Zuora
|
38
|
+
# as it tries to identify the offending field.
|
39
|
+
#
|
40
|
+
# See:
|
41
|
+
# - https://github.com/zendesk/iron_bank/pull/107
|
34
42
|
fields = IronBank::Schema.excluded_fields.sort.to_h
|
35
43
|
|
36
44
|
File.write(destination, Psych.dump(fields))
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Describe
|
5
|
+
# NOTE: Beware there could be a performance hit as the search repeatedly
|
6
|
+
# executes queries to perform the search for invalid fields in the query
|
7
|
+
# included in the query statement.
|
8
|
+
|
9
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
10
|
+
class ExcludedFields::DeduceFromQuery
|
11
|
+
INVALID_OBJECT_ID = "InvalidObjectId"
|
12
|
+
|
13
|
+
private_class_method :new
|
14
|
+
|
15
|
+
def self.call(object)
|
16
|
+
new(object).call
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
query_fields = object.query_fields.clone
|
21
|
+
divide_and_execute(query_fields)
|
22
|
+
object.query_fields.concat(query_fields)
|
23
|
+
invalid_fields
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :object, :valid_fields, :invalid_fields
|
29
|
+
|
30
|
+
def initialize(object)
|
31
|
+
@object = object
|
32
|
+
@valid_fields = []
|
33
|
+
@invalid_fields = []
|
34
|
+
end
|
35
|
+
|
36
|
+
def divide_and_execute(query_fields)
|
37
|
+
# clear state before queries
|
38
|
+
object.query_fields.clear
|
39
|
+
|
40
|
+
# we repeat dividing until only a single field remains
|
41
|
+
invalid_fields.push(query_fields.pop) if query_fields.one?
|
42
|
+
return if query_fields.empty?
|
43
|
+
|
44
|
+
left, right = divide_fields(query_fields)
|
45
|
+
execute_or_divide_again(left)
|
46
|
+
execute_or_divide_again(right)
|
47
|
+
end
|
48
|
+
|
49
|
+
def divide_fields(query_fields)
|
50
|
+
mid = query_fields.size / 2
|
51
|
+
[query_fields[0..mid - 1], query_fields[mid..]]
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute_or_divide_again(fields)
|
55
|
+
if execute_query(fields)
|
56
|
+
valid_fields.concat(fields)
|
57
|
+
else
|
58
|
+
divide_and_execute(fields)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute_query(fields)
|
63
|
+
object.query_fields.concat(valid_fields + fields)
|
64
|
+
object.where({ id: INVALID_OBJECT_ID })
|
65
|
+
true
|
66
|
+
rescue IronBank::InternalServerError
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IronBank
|
4
|
+
module Describe
|
5
|
+
# Extracts invalid fields from an exception message.
|
6
|
+
# Returns from a call if an exception message does not contain invalid field
|
7
|
+
|
8
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
9
|
+
class ExcludedFields::ExtractFromMessage
|
10
|
+
FAULT_FIELD_MESSAGES = Regexp.union(
|
11
|
+
# Generic fault field
|
12
|
+
/invalid field for query: \w+\.(\w+)/,
|
13
|
+
# Invoice Bill Run is not selectable in ZOQL
|
14
|
+
/Cannot use the (BillRunId) field in the select clause/,
|
15
|
+
# Invoice Body, implemented as a separate call
|
16
|
+
/Can only query one invoice (body) at a time/,
|
17
|
+
# Catalog tier should only query the price field
|
18
|
+
/use Price or (DiscountAmount) or (DiscountPercentage)/,
|
19
|
+
# Catalog plan currencies, implemented as a separate call
|
20
|
+
/When querying for (active currencies)/,
|
21
|
+
# Catalog charge rollover balance
|
22
|
+
/You can only query (RolloverBalance) in particular/,
|
23
|
+
# (Subscription) charge should only query the price field
|
24
|
+
/
|
25
|
+
(OveragePrice),
|
26
|
+
\ Price,
|
27
|
+
\ (IncludedUnits),
|
28
|
+
\ (DiscountAmount)
|
29
|
+
\ or\ (DiscountPercentage)
|
30
|
+
/x
|
31
|
+
).freeze
|
32
|
+
|
33
|
+
private_class_method :new
|
34
|
+
|
35
|
+
def self.call(message)
|
36
|
+
new(message).call
|
37
|
+
end
|
38
|
+
|
39
|
+
def call
|
40
|
+
return unless FAULT_FIELD_MESSAGES.match(message)
|
41
|
+
|
42
|
+
Regexp.last_match.captures.compact.
|
43
|
+
map { |capture| capture.delete(" ") }
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :message
|
49
|
+
|
50
|
+
def initialize(message)
|
51
|
+
@message = message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
55
|
+
end
|
56
|
+
end
|
@@ -5,32 +5,10 @@ module IronBank
|
|
5
5
|
# Returns an array of non-queryable fields for the given object in the
|
6
6
|
# current Zuora tenant, despites Zuora clearly marking these fields as
|
7
7
|
# `<selectable>true</true>` in their Describe API... /rant
|
8
|
-
#
|
9
8
|
class ExcludedFields
|
10
9
|
extend Forwardable
|
11
10
|
|
12
|
-
|
13
|
-
# Generic fault field
|
14
|
-
/invalid field for query: \w+\.(\w+)/,
|
15
|
-
# Invoice Bill Run is not selectable in ZOQL
|
16
|
-
/Cannot use the (BillRunId) field in the select clause/,
|
17
|
-
# Invoice Body, implemented as a separate call
|
18
|
-
/Can only query one invoice (body) at a time/,
|
19
|
-
# Catalog tier should only query the price field
|
20
|
-
/use Price or (DiscountAmount) or (DiscountPercentage)/,
|
21
|
-
# Catalog plan currencies, implemented as a separate call
|
22
|
-
/When querying for (active currencies)/,
|
23
|
-
# Catalog charge rollover balance
|
24
|
-
/You can only query (RolloverBalance) in particular/,
|
25
|
-
# (Subscription) charge should only query the price field
|
26
|
-
%r{
|
27
|
-
(OveragePrice),
|
28
|
-
\ Price,
|
29
|
-
\ (IncludedUnits),
|
30
|
-
\ (DiscountAmount)
|
31
|
-
\ or\ (DiscountPercentage)
|
32
|
-
}x
|
33
|
-
).freeze
|
11
|
+
INVALID_OBJECT_ID = "InvalidObjectId"
|
34
12
|
|
35
13
|
private_class_method :new
|
36
14
|
|
@@ -39,43 +17,36 @@ module IronBank
|
|
39
17
|
end
|
40
18
|
|
41
19
|
def call
|
42
|
-
|
43
|
-
|
20
|
+
remove_invalid_fields until valid_query?
|
44
21
|
(excluded_fields - single_resource_query_fields).sort
|
45
22
|
end
|
46
23
|
|
47
24
|
private
|
48
25
|
|
49
|
-
|
50
|
-
|
51
|
-
attr_reader :object_name, :last_failed_fields
|
26
|
+
attr_reader :object_name, :invalid_fields
|
52
27
|
|
53
28
|
def_delegators "IronBank.logger", :info
|
54
29
|
def_delegators :object, :single_resource_query_fields
|
55
30
|
|
56
31
|
def initialize(object_name)
|
57
|
-
@object_name
|
58
|
-
@
|
32
|
+
@object_name = object_name
|
33
|
+
@invalid_fields = []
|
59
34
|
end
|
60
35
|
|
61
36
|
def object
|
62
|
-
IronBank::Resources.const_get(object_name)
|
37
|
+
@object ||= IronBank::Resources.const_get(object_name)
|
63
38
|
end
|
64
39
|
|
65
40
|
def excluded_fields
|
66
41
|
@excluded_fields ||= object.excluded_fields.dup
|
67
42
|
end
|
68
43
|
|
69
|
-
def
|
70
|
-
query_fields
|
71
|
-
|
44
|
+
def remove_invalid_fields
|
45
|
+
query_fields = object.query_fields
|
72
46
|
failed_fields = query_fields.select do |field|
|
73
|
-
|
47
|
+
invalid_fields.any? { |failed| field.casecmp?(failed) }
|
74
48
|
end
|
75
|
-
|
76
49
|
excluded_fields.push(*failed_fields)
|
77
|
-
|
78
|
-
# Remove the field for the next query
|
79
50
|
query_fields.delete_if { |field| failed_fields.include?(field) }
|
80
51
|
end
|
81
52
|
|
@@ -83,30 +54,16 @@ module IronBank
|
|
83
54
|
# Querying using the ID (which is an indexed field) should return an
|
84
55
|
# empty collection very quickly when successful
|
85
56
|
object.where({ id: INVALID_OBJECT_ID })
|
86
|
-
|
87
57
|
info "Successful query for #{object_name}"
|
88
|
-
|
89
58
|
true
|
90
|
-
rescue IronBank::
|
91
|
-
@
|
92
|
-
|
59
|
+
rescue IronBank::BadRequestError, InternalServerError => e
|
60
|
+
@invalid_fields = Array(
|
61
|
+
ExtractFromMessage.call(e.message) ||
|
62
|
+
DeduceFromQuery.call(object)
|
63
|
+
)
|
64
|
+
info "Invalid fields '#{@invalid_fields}' for #{object_name} query"
|
93
65
|
false
|
94
66
|
end
|
95
|
-
|
96
|
-
def extract_fields_from_exception(exception)
|
97
|
-
message = exception.message
|
98
|
-
|
99
|
-
raise "Could not parse error message: #{message}" unless FAULT_FIELD_MESSAGES.match(message)
|
100
|
-
|
101
|
-
failed_fields = Regexp.last_match.
|
102
|
-
captures.
|
103
|
-
compact.
|
104
|
-
map { |capture| capture.delete(" ") }
|
105
|
-
|
106
|
-
info "Invalid fields '#{failed_fields}' for #{object_name} query"
|
107
|
-
|
108
|
-
failed_fields
|
109
|
-
end
|
110
67
|
end
|
111
68
|
end
|
112
69
|
end
|
data/lib/iron_bank/schema.rb
CHANGED
@@ -39,6 +39,14 @@ module IronBank
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def self.excluded_fields
|
42
|
+
# NOTE: In some instances the error message from Zuora will not include the
|
43
|
+
# unqueryable fields that need to be excluded. When that happens IronBank's
|
44
|
+
# strategy will be to perform a binary search through the fields listed in the
|
45
|
+
# query -- at the cost of performance due to repeated requests sent to Zuora
|
46
|
+
# as it tries to identify the offending field.
|
47
|
+
#
|
48
|
+
# See:
|
49
|
+
# - https://github.com/zendesk/iron_bank/pull/107
|
42
50
|
@excluded_fields ||= IronBank::Resources.constants.each.with_object({}) do |resource, fields|
|
43
51
|
fields[resource.to_s] =
|
44
52
|
IronBank::Describe::ExcludedFields.call(object_name: resource)
|
data/lib/iron_bank/version.rb
CHANGED
data/lib/iron_bank.rb
CHANGED
@@ -83,6 +83,8 @@ require "iron_bank/authentications/token"
|
|
83
83
|
require "iron_bank/client"
|
84
84
|
require "iron_bank/describe/field"
|
85
85
|
require "iron_bank/describe/excluded_fields"
|
86
|
+
require "iron_bank/describe/excluded_fields/deduce_from_query"
|
87
|
+
require "iron_bank/describe/excluded_fields/extract_from_message"
|
86
88
|
require "iron_bank/describe/object"
|
87
89
|
require "iron_bank/describe/related"
|
88
90
|
require "iron_bank/describe/tenant"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iron_bank
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.4.
|
4
|
+
version: 5.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mickael Pham
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2024-01-02 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: faraday
|
@@ -107,6 +107,8 @@ files:
|
|
107
107
|
- lib/iron_bank/configuration.rb
|
108
108
|
- lib/iron_bank/csv.rb
|
109
109
|
- lib/iron_bank/describe/excluded_fields.rb
|
110
|
+
- lib/iron_bank/describe/excluded_fields/deduce_from_query.rb
|
111
|
+
- lib/iron_bank/describe/excluded_fields/extract_from_message.rb
|
110
112
|
- lib/iron_bank/describe/field.rb
|
111
113
|
- lib/iron_bank/describe/object.rb
|
112
114
|
- lib/iron_bank/describe/related.rb
|