splitclient-rb 6.2.0 → 6.3.0.pre.rc1
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/CHANGES.txt +9 -0
- data/NEWS +9 -0
- data/lib/splitclient-rb/clients/localhost_split_client.rb +126 -34
- data/lib/splitclient-rb/clients/split_client.rb +142 -104
- data/lib/splitclient-rb/engine/api/client.rb +2 -2
- data/lib/splitclient-rb/engine/parser/evaluator.rb +12 -7
- data/lib/splitclient-rb/localhost_split_factory.rb +1 -1
- data/lib/splitclient-rb/localhost_utils.rb +24 -1
- data/lib/splitclient-rb/managers/localhost_split_manager.rb +21 -6
- data/lib/splitclient-rb/managers/split_manager.rb +2 -1
- data/lib/splitclient-rb/split_factory_builder.rb +14 -2
- data/lib/splitclient-rb/validators.rb +28 -28
- data/lib/splitclient-rb/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b5098cf06d8181cb623522e84629c6df7cc454f7b9a636ec4f872935f73675a
|
4
|
+
data.tar.gz: 835d12570cfc5d16a227a8f39279ed71c874c0361cdb1bc4ee07979d168d3aa8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 357ab23aa02a12191044afc3069000dd41d3c67e36ccb62417c6aef8a9e87baf08573ba82a96068e5b5270f138801cfab2e61ccb2a781d0a40e815a31babf642
|
7
|
+
data.tar.gz: f607e6c9b7e8484f86f1b1f6af36f3dd0cff3bb4edba5fdcda6de6f6a6b67452c8894938065c8d8f12025833eee72793f568a544de42da547f84133717494422
|
data/CHANGES.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
6.3.0 (Apr 22, 2019)
|
2
|
+
|
3
|
+
- Added Dynamic Configurations support through two new methods that mimick the regular ones, changing the type of what is returned.
|
4
|
+
- get_treatment_with_config: Same as get_treatment but returning the treatment with it's config.
|
5
|
+
- get_treatments_with_config: Same as get_treatments, but instead of a map of string it returns a map of treatments with config.
|
6
|
+
- Added configs to SplitViews returned by the manager module.
|
7
|
+
- Updated localhost mode. Now besides supporting the old text files with `.split` extension (to be deprecated soon), we support YAML (.yaml/.yml) files where you can
|
8
|
+
define configurations for your treatments and also whitelisted keys. Read more in our docs!
|
9
|
+
|
1
10
|
6.2.0 (Mar 7th, 2019)
|
2
11
|
- Reworked SplitClient#destroy to ensure events, impressions and metrics are sent to Split backend when called
|
3
12
|
- Ensured destroy is called when keyboard interrupts are sent to the application
|
data/NEWS
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
6.3.0 (Apr 22, 2019)
|
2
|
+
|
3
|
+
- Added Dynamic Configurations support through two new methods that mimick the regular ones, changing the type of what is returned.
|
4
|
+
- get_treatment_with_config: Same as get_treatment but returning the treatment with it's config.
|
5
|
+
- get_treatments_with_config: Same as get_treatments, but instead of a map of string it returns a map of treatments with config.
|
6
|
+
- Added configs to SplitViews returned by the manager module.
|
7
|
+
- Updated localhost mode. Now besides supporting the old text files with `.split` extension (to be deprecated soon), we support YAML (.yaml/.yml) files where you can
|
8
|
+
define configurations for your treatments and also whitelisted keys. Read more in our docs!
|
9
|
+
|
1
10
|
6.2.0
|
2
11
|
Ensure SDK flushes information to Split servers on client destroy
|
3
12
|
Fix for compatibility issue between Faraday < 0.13 and net-http-persistent 3
|
@@ -27,31 +27,56 @@ module SplitIoClient
|
|
27
27
|
'ruby-'+SplitIoClient::VERSION
|
28
28
|
end
|
29
29
|
|
30
|
+
#
|
31
|
+
# obtains the treatments and configs for a given set of features
|
32
|
+
#
|
33
|
+
# @param key [string] evaluation key, only used with yaml split files
|
34
|
+
# @param split_names [array] name of the features being validated
|
35
|
+
# @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
|
36
|
+
#
|
37
|
+
# @return [hash] map of treatments (split_name, treatment)
|
38
|
+
def get_treatments_with_config(key, split_names, attributes = nil)
|
39
|
+
get_localhost_treatments(key, split_names, attributes, 'get_treatments_with_config')
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# obtains the treatments for a given set of features
|
44
|
+
#
|
45
|
+
# @param key [string] evaluation key, only used with yaml split files
|
46
|
+
# @param split_names [array] name of the features being validated
|
47
|
+
# @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
|
48
|
+
#
|
49
|
+
# @return [hash] map of treatments (split_name, treatment_name)
|
30
50
|
def get_treatments(key, split_names, attributes = nil)
|
31
|
-
|
32
|
-
|
33
|
-
|
51
|
+
treatments = get_localhost_treatments(key, split_names, attributes)
|
52
|
+
return treatments if treatments.nil?
|
53
|
+
keys = treatments.keys
|
54
|
+
treats = treatments.map { |_,t| t[:treatment] }
|
55
|
+
Hash[keys.zip(treats)]
|
34
56
|
end
|
35
57
|
|
36
58
|
#
|
37
59
|
# obtains the treatment for a given feature
|
38
60
|
#
|
39
|
-
# @param
|
40
|
-
# @param
|
61
|
+
# @param key [string] evaluation key, only used with yaml split files
|
62
|
+
# @param split_name [string] name of the feature that is being validated
|
63
|
+
# @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
|
41
64
|
#
|
42
|
-
# @return [
|
43
|
-
def get_treatment(
|
44
|
-
|
45
|
-
|
46
|
-
return SplitIoClient::Engine::Models::Treatment::CONTROL
|
47
|
-
end
|
48
|
-
|
49
|
-
unless feature
|
50
|
-
SplitIoClient.configuration.logger.warn('feature was null for id: ' + id)
|
51
|
-
return SplitIoClient::Engine::Models::Treatment::CONTROL
|
52
|
-
end
|
65
|
+
# @return [string] corresponding treatment
|
66
|
+
def get_treatment(key, split_name, attributes = nil)
|
67
|
+
get_localhost_treatment(key, split_name, attributes)[:treatment]
|
68
|
+
end
|
53
69
|
|
54
|
-
|
70
|
+
#
|
71
|
+
# obtains the treatment and config for a given feature
|
72
|
+
#
|
73
|
+
# @param key [string] evaluation key, only used with yaml split files
|
74
|
+
# @param split_name [string] name of the feature that is being validated
|
75
|
+
# @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls
|
76
|
+
#
|
77
|
+
# @return [hash] corresponding treatment and config
|
78
|
+
def get_treatment_with_config(key, split_name, attributes = nil)
|
79
|
+
get_localhost_treatment(key, split_name, attributes, 'get_treatment_with_config')
|
55
80
|
end
|
56
81
|
|
57
82
|
def track
|
@@ -59,17 +84,6 @@ module SplitIoClient
|
|
59
84
|
|
60
85
|
private
|
61
86
|
|
62
|
-
#
|
63
|
-
# auxiliary method to get the treatments avoding exceptions
|
64
|
-
#
|
65
|
-
# @param id [string] user id
|
66
|
-
# @param feature [string] name of the feature that is being validated
|
67
|
-
#
|
68
|
-
# @return [Treatment] tretment constant value
|
69
|
-
def get_treatment_without_exception_handling(id, feature, attributes = nil)
|
70
|
-
get_treatment(id, feature, attributes)
|
71
|
-
end
|
72
|
-
|
73
87
|
#
|
74
88
|
# method to check if the sdk is running in localhost mode based on api key
|
75
89
|
#
|
@@ -78,14 +92,92 @@ module SplitIoClient
|
|
78
92
|
true
|
79
93
|
end
|
80
94
|
|
95
|
+
# @param key [string] evaluation key, only used with yaml split files
|
96
|
+
# @param split_name [string] name of the feature that is being validated
|
81
97
|
#
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
98
|
+
# @return [Hash] corresponding treatment and config, control otherwise
|
99
|
+
def get_localhost_treatment(key, split_name, attributes, calling_method = 'get_treatment')
|
100
|
+
control_treatment = { label: Engine::Models::Label::EXCEPTION, treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, config: nil }
|
101
|
+
parsed_control_treatment = parsed_treatment(control_treatment)
|
102
|
+
|
103
|
+
bucketing_key, matching_key = keys_from_key(key)
|
104
|
+
return parsed_control_treatment unless SplitIoClient::Validators.valid_get_treatment_parameters(calling_method, key, split_name, matching_key, bucketing_key, attributes)
|
105
|
+
|
106
|
+
sanitized_split_name = split_name.to_s.strip
|
87
107
|
|
88
|
-
|
108
|
+
if split_name.to_s != sanitized_split_name
|
109
|
+
SplitIoClient.configuration.logger.warn("get_treatment: split_name #{split_name} has extra whitespace, trimming")
|
110
|
+
split_name = sanitized_split_name
|
111
|
+
end
|
112
|
+
|
113
|
+
treatment = @localhost_mode_features.select { |h| h[:feature] == split_name && has_key(h[:keys], key) }.last
|
114
|
+
|
115
|
+
if treatment.nil?
|
116
|
+
treatment = @localhost_mode_features.select { |h| h[:feature] == split_name && h[:keys] == nil }.last
|
117
|
+
end
|
118
|
+
|
119
|
+
if treatment && treatment[:treatment]
|
120
|
+
{
|
121
|
+
treatment: treatment[:treatment],
|
122
|
+
config: treatment[:config]
|
123
|
+
}
|
124
|
+
else
|
125
|
+
parsed_control_treatment
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def get_localhost_treatments(key, split_names, attributes = nil, calling_method = 'get_treatments')
|
130
|
+
return nil unless SplitIoClient::Validators.valid_get_treatments_parameters(calling_method, split_names)
|
131
|
+
|
132
|
+
sanitized_split_names = sanitize_split_names(calling_method, split_names)
|
133
|
+
|
134
|
+
if sanitized_split_names.empty?
|
135
|
+
SplitIoClient.configuration.logger.error("#{calling_method}: split_names must be a non-empty Array")
|
136
|
+
return {}
|
137
|
+
end
|
138
|
+
|
139
|
+
split_names.each_with_object({}) do |split_name, memo|
|
140
|
+
memo.merge!(split_name => get_treatment_with_config(key, split_name, attributes))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def sanitize_split_names(calling_method, split_names)
|
145
|
+
split_names.compact.uniq.select do |split_name|
|
146
|
+
if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty?
|
147
|
+
true
|
148
|
+
elsif split_name.is_a?(String) && split_name.empty?
|
149
|
+
SplitIoClient.configuration.logger.warn("#{calling_method}: you passed an empty split_name, split_name must be a non-empty String or a Symbol")
|
150
|
+
false
|
151
|
+
else
|
152
|
+
SplitIoClient.configuration.logger.warn("#{calling_method}: you passed an invalid split_name, split_name must be a non-empty String or a Symbol")
|
153
|
+
false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def parsed_treatment(treatment_data)
|
159
|
+
{
|
160
|
+
treatment: treatment_data[:treatment],
|
161
|
+
config: treatment_data[:config]
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
def has_key(keys, key)
|
166
|
+
case keys
|
167
|
+
when Array then keys.include? key
|
168
|
+
when String then keys == key
|
169
|
+
else
|
170
|
+
false
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def keys_from_key(key)
|
175
|
+
case key
|
176
|
+
when Hash
|
177
|
+
key.values_at(:bucketing_key, :matching_key).map { |k| k.nil? ? nil : k }
|
178
|
+
else
|
179
|
+
[nil, key].map { |k| k.nil? ? nil : k }
|
180
|
+
end
|
89
181
|
end
|
90
182
|
end
|
91
183
|
end
|
@@ -18,109 +18,35 @@ module SplitIoClient
|
|
18
18
|
@adapter = adapter
|
19
19
|
end
|
20
20
|
|
21
|
-
def get_treatments(key, split_names, attributes = {})
|
22
|
-
return nil unless SplitIoClient::Validators.valid_get_treatments_parameters(split_names)
|
23
|
-
|
24
|
-
sanitized_split_names = sanitize_split_names(split_names)
|
25
|
-
|
26
|
-
if sanitized_split_names.empty?
|
27
|
-
SplitIoClient.configuration.logger.error('get_treatments: split_names must be a non-empty Array')
|
28
|
-
return {}
|
29
|
-
end
|
30
|
-
|
31
|
-
bucketing_key, matching_key = keys_from_key(key)
|
32
|
-
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
|
33
|
-
matching_key = matching_key ? matching_key.to_s : nil
|
34
|
-
|
35
|
-
evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, true)
|
36
|
-
start = Time.now
|
37
|
-
treatments_labels_change_numbers =
|
38
|
-
@splits_repository.get_splits(sanitized_split_names).each_with_object({}) do |(name, data), memo|
|
39
|
-
memo.merge!(name => get_treatment(key, name, attributes, data, false, true, evaluator))
|
40
|
-
end
|
41
|
-
latency = (Time.now - start) * 1000.0
|
42
|
-
# Measure
|
43
|
-
@adapter.metrics.time('sdk.get_treatments', latency)
|
44
|
-
|
45
|
-
unless SplitIoClient.configuration.disable_impressions
|
46
|
-
time = (Time.now.to_f * 1000.0).to_i
|
47
|
-
@impressions_repository.add_bulk(
|
48
|
-
matching_key, bucketing_key, treatments_labels_change_numbers, time
|
49
|
-
)
|
50
|
-
|
51
|
-
route_impressions(sanitized_split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
|
52
|
-
end
|
53
|
-
|
54
|
-
split_names_keys = treatments_labels_change_numbers.keys
|
55
|
-
treatments = treatments_labels_change_numbers.values.map { |v| v[:treatment] }
|
56
|
-
|
57
|
-
Hash[split_names_keys.zip(treatments)]
|
58
|
-
end
|
59
|
-
|
60
|
-
#
|
61
|
-
# obtains the treatment for a given feature
|
62
|
-
#
|
63
|
-
# @param key [String/Hash] user id or hash with matching_key/bucketing_key
|
64
|
-
# @param split_name [String/Array] name of the feature that is being validated or array of them
|
65
|
-
# @param attributes [Hash] attributes to pass to the treatment class
|
66
|
-
# @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
|
67
|
-
# @param store_impressions [Boolean] impressions aren't stored if this flag is false
|
68
|
-
# @param multiple [Hash] internal flag to signal if method is called by get_treatments
|
69
|
-
# @param evaluator [Evaluator] Evaluator class instance, used to cache treatments
|
70
|
-
#
|
71
|
-
# @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
|
72
21
|
def get_treatment(
|
73
22
|
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
|
74
23
|
multiple = false, evaluator = nil
|
75
24
|
)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
return parsed_control_treatment unless valid_client && SplitIoClient::Validators.valid_get_treatment_parameters(key, split_name, matching_key, bucketing_key, attributes)
|
83
|
-
|
84
|
-
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
|
85
|
-
matching_key = matching_key.to_s
|
86
|
-
sanitized_split_name = split_name.to_s.strip
|
87
|
-
|
88
|
-
if split_name.to_s != sanitized_split_name
|
89
|
-
SplitIoClient.configuration.logger.warn("get_treatment: split_name #{split_name} has extra whitespace, trimming")
|
90
|
-
split_name = sanitized_split_name
|
25
|
+
treatment = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator)
|
26
|
+
if multiple
|
27
|
+
treatment.tap { |t| t.delete(:config) }
|
28
|
+
else
|
29
|
+
treatment[:treatment]
|
91
30
|
end
|
31
|
+
end
|
92
32
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
if split.nil?
|
101
|
-
SplitIoClient.configuration.logger.warn("split_name: #{split_name} does not exist. Returning CONTROL")
|
102
|
-
return parsed_control_treatment
|
103
|
-
end
|
104
|
-
|
105
|
-
treatment_data =
|
106
|
-
evaluator.call(
|
107
|
-
{ bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
|
108
|
-
)
|
109
|
-
|
110
|
-
latency = (Time.now - start) * 1000.0
|
111
|
-
store_impression(split_name, matching_key, bucketing_key, treatment_data, store_impressions, attributes)
|
112
|
-
|
113
|
-
# Measure
|
114
|
-
@adapter.metrics.time('sdk.get_treatment', latency) unless multiple
|
115
|
-
rescue StandardError => error
|
116
|
-
SplitIoClient.configuration.log_found_exception(__method__.to_s, error)
|
117
|
-
|
118
|
-
store_impression(split_name, matching_key, bucketing_key, control_treatment, store_impressions, attributes)
|
33
|
+
def get_treatment_with_config(
|
34
|
+
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
|
35
|
+
multiple = false, evaluator = nil
|
36
|
+
)
|
37
|
+
treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment_with_config')
|
38
|
+
end
|
119
39
|
|
120
|
-
|
121
|
-
|
40
|
+
def get_treatments(key, split_names, attributes = {})
|
41
|
+
treatments = treatments(key, split_names, attributes)
|
42
|
+
return treatments if treatments.nil?
|
43
|
+
keys = treatments.keys
|
44
|
+
treats = treatments.map { |_,t| t[:treatment] }
|
45
|
+
Hash[keys.zip(treats)]
|
46
|
+
end
|
122
47
|
|
123
|
-
|
48
|
+
def get_treatments_with_config(key, split_names, attributes = {})
|
49
|
+
treatments(key, split_names, attributes,'get_treatments_with_config')
|
124
50
|
end
|
125
51
|
|
126
52
|
def destroy
|
@@ -198,8 +124,8 @@ module SplitIoClient
|
|
198
124
|
end
|
199
125
|
|
200
126
|
def keys_from_key(key)
|
201
|
-
case key
|
202
|
-
when
|
127
|
+
case key
|
128
|
+
when Hash
|
203
129
|
key.values_at(:bucketing_key, :matching_key).map { |k| k.nil? ? nil : k }
|
204
130
|
else
|
205
131
|
[nil, key].map { |k| k.nil? ? nil : k }
|
@@ -208,25 +134,29 @@ module SplitIoClient
|
|
208
134
|
|
209
135
|
def parsed_treatment(multiple, treatment_data)
|
210
136
|
if multiple
|
137
|
+
{
|
138
|
+
treatment: treatment_data[:treatment],
|
139
|
+
label: treatment_data[:label],
|
140
|
+
change_number: treatment_data[:change_number],
|
141
|
+
config: treatment_data[:config]
|
142
|
+
}
|
143
|
+
else
|
211
144
|
{
|
212
145
|
treatment: treatment_data[:treatment],
|
213
|
-
|
214
|
-
change_number: treatment_data[:change_number]
|
146
|
+
config: treatment_data[:config]
|
215
147
|
}
|
216
|
-
else
|
217
|
-
treatment_data[:treatment]
|
218
148
|
end
|
219
149
|
end
|
220
150
|
|
221
|
-
def sanitize_split_names(split_names)
|
151
|
+
def sanitize_split_names(calling_method, split_names)
|
222
152
|
split_names.compact.uniq.select do |split_name|
|
223
153
|
if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty?
|
224
154
|
true
|
225
155
|
elsif split_name.is_a?(String) && split_name.empty?
|
226
|
-
SplitIoClient.configuration.logger.warn(
|
156
|
+
SplitIoClient.configuration.logger.warn("#{calling_method}: you passed an empty split_name, split_name must be a non-empty String or a Symbol")
|
227
157
|
false
|
228
158
|
else
|
229
|
-
SplitIoClient.configuration.logger.warn(
|
159
|
+
SplitIoClient.configuration.logger.warn("#{calling_method}: you passed an invalid split_name, split_name must be a non-empty String or a Symbol")
|
230
160
|
false
|
231
161
|
end
|
232
162
|
end
|
@@ -241,5 +171,113 @@ module SplitIoClient
|
|
241
171
|
end
|
242
172
|
SplitIoClient.configuration.valid_mode
|
243
173
|
end
|
174
|
+
|
175
|
+
def treatments(key, split_names, attributes = {}, calling_method = 'get_treatments')
|
176
|
+
return nil unless SplitIoClient::Validators.valid_get_treatments_parameters(calling_method, split_names)
|
177
|
+
|
178
|
+
sanitized_split_names = sanitize_split_names(calling_method, split_names)
|
179
|
+
|
180
|
+
if sanitized_split_names.empty?
|
181
|
+
SplitIoClient.configuration.logger.error("#{calling_method}: split_names must be a non-empty Array")
|
182
|
+
return {}
|
183
|
+
end
|
184
|
+
|
185
|
+
bucketing_key, matching_key = keys_from_key(key)
|
186
|
+
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
|
187
|
+
matching_key = matching_key ? matching_key.to_s : nil
|
188
|
+
|
189
|
+
evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, true)
|
190
|
+
start = Time.now
|
191
|
+
treatments_labels_change_numbers =
|
192
|
+
@splits_repository.get_splits(sanitized_split_names).each_with_object({}) do |(name, data), memo|
|
193
|
+
memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator))
|
194
|
+
end
|
195
|
+
latency = (Time.now - start) * 1000.0
|
196
|
+
# Measure
|
197
|
+
@adapter.metrics.time('sdk.' + calling_method, latency)
|
198
|
+
|
199
|
+
unless SplitIoClient.configuration.disable_impressions
|
200
|
+
time = (Time.now.to_f * 1000.0).to_i
|
201
|
+
@impressions_repository.add_bulk(
|
202
|
+
matching_key, bucketing_key, treatments_labels_change_numbers, time
|
203
|
+
)
|
204
|
+
|
205
|
+
route_impressions(sanitized_split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
|
206
|
+
end
|
207
|
+
|
208
|
+
split_names_keys = treatments_labels_change_numbers.keys
|
209
|
+
treatments = treatments_labels_change_numbers.values.map do |v|
|
210
|
+
{
|
211
|
+
treatment: v[:treatment],
|
212
|
+
config: v[:config]
|
213
|
+
}
|
214
|
+
end
|
215
|
+
Hash[split_names_keys.zip(treatments)]
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# obtains the treatment for a given feature
|
220
|
+
#
|
221
|
+
# @param key [String/Hash] user id or hash with matching_key/bucketing_key
|
222
|
+
# @param split_name [String/Array] name of the feature that is being validated or array of them
|
223
|
+
# @param attributes [Hash] attributes to pass to the treatment class
|
224
|
+
# @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
|
225
|
+
# @param store_impressions [Boolean] impressions aren't stored if this flag is false
|
226
|
+
# @param multiple [Hash] internal flag to signal if method is called by get_treatments
|
227
|
+
# @param evaluator [Evaluator] Evaluator class instance, used to cache treatments
|
228
|
+
#
|
229
|
+
# @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
|
230
|
+
def treatment(
|
231
|
+
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
|
232
|
+
multiple = false, evaluator = nil, calling_method = 'get_treatment'
|
233
|
+
)
|
234
|
+
control_treatment = { label: Engine::Models::Label::EXCEPTION, treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, config: nil }
|
235
|
+
parsed_control_treatment = parsed_treatment(multiple, control_treatment)
|
236
|
+
|
237
|
+
bucketing_key, matching_key = keys_from_key(key)
|
238
|
+
|
239
|
+
return parsed_control_treatment unless valid_client && SplitIoClient::Validators.valid_get_treatment_parameters(calling_method, key, split_name, matching_key, bucketing_key, attributes)
|
240
|
+
|
241
|
+
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
|
242
|
+
matching_key = matching_key.to_s
|
243
|
+
sanitized_split_name = split_name.to_s.strip
|
244
|
+
|
245
|
+
if split_name.to_s != sanitized_split_name
|
246
|
+
SplitIoClient.configuration.logger.warn("#{calling_method}: split_name #{split_name} has extra whitespace, trimming")
|
247
|
+
split_name = sanitized_split_name
|
248
|
+
end
|
249
|
+
|
250
|
+
evaluator ||= Engine::Parser::Evaluator.new(@segments_repository, @splits_repository)
|
251
|
+
|
252
|
+
begin
|
253
|
+
start = Time.now
|
254
|
+
|
255
|
+
split = multiple ? split_data : @splits_repository.get_split(split_name)
|
256
|
+
|
257
|
+
if split.nil?
|
258
|
+
SplitIoClient.configuration.logger.warn("split_name: #{split_name} does not exist. Returning CONTROL")
|
259
|
+
return parsed_control_treatment
|
260
|
+
end
|
261
|
+
|
262
|
+
treatment_data =
|
263
|
+
evaluator.call(
|
264
|
+
{ bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
|
265
|
+
)
|
266
|
+
|
267
|
+
latency = (Time.now - start) * 1000.0
|
268
|
+
store_impression(split_name, matching_key, bucketing_key, treatment_data, store_impressions, attributes)
|
269
|
+
|
270
|
+
# Measure
|
271
|
+
@adapter.metrics.time('sdk.' + calling_method, latency) unless multiple
|
272
|
+
rescue StandardError => error
|
273
|
+
SplitIoClient.configuration.log_found_exception(__method__.to_s, error)
|
274
|
+
|
275
|
+
store_impression(split_name, matching_key, bucketing_key, control_treatment, store_impressions, attributes)
|
276
|
+
|
277
|
+
return parsed_control_treatment
|
278
|
+
end
|
279
|
+
|
280
|
+
parsed_treatment(multiple, treatment_data)
|
281
|
+
end
|
244
282
|
end
|
245
283
|
end
|
@@ -18,7 +18,7 @@ module SplitIoClient
|
|
18
18
|
end
|
19
19
|
rescue StandardError => e
|
20
20
|
SplitIoClient.configuration.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
|
21
|
-
raise 'Split SDK failed to connect to backend to retrieve information'
|
21
|
+
raise e, 'Split SDK failed to connect to backend to retrieve information', e.backtrace
|
22
22
|
end
|
23
23
|
|
24
24
|
def post_api(url, api_key, data, headers = {}, params = {})
|
@@ -37,7 +37,7 @@ module SplitIoClient
|
|
37
37
|
end
|
38
38
|
rescue StandardError => e
|
39
39
|
SplitIoClient.configuration.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
|
40
|
-
raise 'Split SDK failed to connect to backend to post information'
|
40
|
+
raise e, 'Split SDK failed to connect to backend to post information', e.backtrace
|
41
41
|
end
|
42
42
|
|
43
43
|
private
|
@@ -26,7 +26,7 @@ module SplitIoClient
|
|
26
26
|
match(split, keys, attributes)
|
27
27
|
end
|
28
28
|
else
|
29
|
-
treatment_hash(Models::Label::KILLED, split[:defaultTreatment], split[:changeNumber])
|
29
|
+
treatment_hash(Models::Label::KILLED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split))
|
30
30
|
end
|
31
31
|
|
32
32
|
@cache[digest] = treatment if cache_result
|
@@ -36,6 +36,11 @@ module SplitIoClient
|
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
|
+
def split_configurations(treatment, split)
|
40
|
+
return nil if split[:configurations].nil?
|
41
|
+
split[:configurations][treatment.to_sym]
|
42
|
+
end
|
43
|
+
|
39
44
|
def match(split, keys, attributes)
|
40
45
|
in_rollout = false
|
41
46
|
key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
|
@@ -52,7 +57,7 @@ module SplitIoClient
|
|
52
57
|
bucket = splitter.bucket(splitter.count_hash(key, split[:trafficAllocationSeed].to_i, legacy_algo))
|
53
58
|
|
54
59
|
if bucket > split[:trafficAllocation]
|
55
|
-
return treatment_hash(Models::Label::NOT_IN_SPLIT, split[:defaultTreatment], split[:changeNumber])
|
60
|
+
return treatment_hash(Models::Label::NOT_IN_SPLIT, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split))
|
56
61
|
end
|
57
62
|
end
|
58
63
|
|
@@ -71,13 +76,13 @@ module SplitIoClient
|
|
71
76
|
result = splitter.get_treatment(key, split[:seed], condition.partitions, split[:algo])
|
72
77
|
|
73
78
|
if result.nil?
|
74
|
-
return treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber])
|
79
|
+
return treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split))
|
75
80
|
else
|
76
|
-
return treatment_hash(c[:label], result, split[:changeNumber])
|
81
|
+
return treatment_hash(c[:label], result, split[:changeNumber],split_configurations(result, split))
|
77
82
|
end
|
78
83
|
end
|
79
84
|
|
80
|
-
treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber])
|
85
|
+
treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split))
|
81
86
|
end
|
82
87
|
|
83
88
|
def matcher_type(condition)
|
@@ -102,8 +107,8 @@ module SplitIoClient
|
|
102
107
|
end
|
103
108
|
end
|
104
109
|
|
105
|
-
def treatment_hash(label, treatment, change_number = nil)
|
106
|
-
{ label: label, treatment: treatment, change_number: change_number }
|
110
|
+
def treatment_hash(label, treatment, change_number = nil, configurations = nil)
|
111
|
+
{ label: label, treatment: treatment, change_number: change_number, config: configurations }
|
107
112
|
end
|
108
113
|
|
109
114
|
def matcher_instance(type, condition, matcher)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module SplitIoClient
|
2
2
|
module LocalhostUtils
|
3
|
+
|
4
|
+
require 'yaml'
|
3
5
|
#
|
4
6
|
# method to set localhost mode features by reading the given .splits
|
5
7
|
#
|
@@ -24,12 +26,33 @@ module SplitIoClient
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def store_features(splits_file)
|
29
|
+
yaml_extensions = [".yml", ".yaml"]
|
30
|
+
if yaml_extensions.include? File.extname(splits_file)
|
31
|
+
store_yaml_features(splits_file)
|
32
|
+
else
|
33
|
+
store_plain_text_features(splits_file)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def store_plain_text_features(splits_file)
|
27
40
|
File.open(splits_file).each do |line|
|
28
41
|
feature, treatment = line.strip.split(' ')
|
29
42
|
|
30
43
|
next if line.start_with?('#') || line.strip.empty?
|
31
44
|
|
32
|
-
@localhost_mode_features << { feature: feature, treatment: treatment }
|
45
|
+
@localhost_mode_features << { feature: feature, treatment: treatment, key: nil, config: nil }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def store_yaml_features(splits_file)
|
50
|
+
YAML.load(File.read(splits_file)).each do |feature|
|
51
|
+
feat_symbolized_keys = feature[feature.keys.first].inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
52
|
+
|
53
|
+
feat_symbolized_keys[:config] = feat_symbolized_keys[:config].to_json
|
54
|
+
|
55
|
+
@localhost_mode_features << { feature: feature.keys.first }.merge(feat_symbolized_keys)
|
33
56
|
end
|
34
57
|
end
|
35
58
|
end
|
@@ -21,15 +21,32 @@ module SplitIoClient
|
|
21
21
|
#
|
22
22
|
# @returns a split view
|
23
23
|
def split(split_name)
|
24
|
-
@localhost_mode_features.
|
24
|
+
features = @localhost_mode_features.find_all { |feat| feat[:feature] == split_name }
|
25
|
+
|
26
|
+
return nil if features.nil?
|
27
|
+
|
28
|
+
treatments = features.map { |feat| feat[:treatment] }
|
29
|
+
|
30
|
+
configs = Hash[ features.map { |feat| [ feat[:treatment].to_sym, feat[:config] ] } ]
|
31
|
+
|
32
|
+
{
|
33
|
+
change_number: nil,
|
34
|
+
killed: false,
|
35
|
+
name: split_name,
|
36
|
+
traffic_type: nil,
|
37
|
+
treatments: treatments,
|
38
|
+
configs: configs
|
39
|
+
}
|
25
40
|
end
|
26
41
|
|
27
42
|
#
|
28
43
|
# method to get the split list from the client
|
29
44
|
#
|
30
|
-
# @returns
|
45
|
+
# @returns Array of split view
|
31
46
|
def splits
|
32
|
-
|
47
|
+
split_names.map do |split_name|
|
48
|
+
split(split_name)
|
49
|
+
end
|
33
50
|
end
|
34
51
|
|
35
52
|
#
|
@@ -37,9 +54,7 @@ module SplitIoClient
|
|
37
54
|
#
|
38
55
|
# @returns [object] array of split names (String)
|
39
56
|
def split_names
|
40
|
-
@localhost_mode_features.
|
41
|
-
memo << split[:feature]
|
42
|
-
end
|
57
|
+
@localhost_mode_features.map{ |feat| feat[:feature]}.uniq
|
43
58
|
end
|
44
59
|
end
|
45
60
|
end
|
@@ -75,7 +75,8 @@ module SplitIoClient
|
|
75
75
|
traffic_type_name: split[:trafficTypeName],
|
76
76
|
killed: split[:killed],
|
77
77
|
treatments: treatments,
|
78
|
-
change_number: split[:changeNumber]
|
78
|
+
change_number: split[:changeNumber],
|
79
|
+
configs: split[:configurations] || {}
|
79
80
|
}
|
80
81
|
end
|
81
82
|
end
|
@@ -5,12 +5,24 @@ module SplitIoClient
|
|
5
5
|
def self.build(api_key, config = {})
|
6
6
|
case api_key
|
7
7
|
when 'localhost'
|
8
|
-
|
8
|
+
SplitIoClient.configure( { logger: config[:logger] } )
|
9
9
|
|
10
|
-
LocalhostSplitFactory.new(
|
10
|
+
LocalhostSplitFactory.new(split_file(config[:split_file]), config[:reload_rate])
|
11
11
|
else
|
12
12
|
SplitFactory.new(api_key, config)
|
13
13
|
end
|
14
14
|
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.split_file(split_file_path)
|
19
|
+
return split_file_path unless split_file_path.nil?
|
20
|
+
|
21
|
+
SplitIoClient.configuration.logger.warn('Localhost mode: .split mocks ' \
|
22
|
+
'will be deprecated soon in favor of YAML files, which provide more ' \
|
23
|
+
'targeting power. Take a look in our documentation.')
|
24
|
+
|
25
|
+
File.join(Dir.home, '.split')
|
26
|
+
end
|
15
27
|
end
|
16
28
|
end
|
@@ -4,16 +4,16 @@ module SplitIoClient
|
|
4
4
|
module Validators
|
5
5
|
extend self
|
6
6
|
|
7
|
-
def valid_get_treatment_parameters(key, split_name, matching_key, bucketing_key, attributes)
|
8
|
-
valid_key?(key) &&
|
9
|
-
valid_split_name?(split_name) &&
|
10
|
-
valid_matching_key?(matching_key) &&
|
11
|
-
valid_bucketing_key?(key, bucketing_key) &&
|
12
|
-
valid_attributes?(attributes)
|
7
|
+
def valid_get_treatment_parameters(method, key, split_name, matching_key, bucketing_key, attributes)
|
8
|
+
valid_key?(method, key) &&
|
9
|
+
valid_split_name?(method, split_name) &&
|
10
|
+
valid_matching_key?(method, matching_key) &&
|
11
|
+
valid_bucketing_key?(method, key, bucketing_key) &&
|
12
|
+
valid_attributes?(method, attributes)
|
13
13
|
end
|
14
14
|
|
15
|
-
def valid_get_treatments_parameters(split_names)
|
16
|
-
valid_split_names?(split_names)
|
15
|
+
def valid_get_treatments_parameters(method, split_names)
|
16
|
+
valid_split_names?(method, split_names)
|
17
17
|
end
|
18
18
|
|
19
19
|
def valid_track_parameters(key, traffic_type_name, event_type, value)
|
@@ -24,7 +24,7 @@ module SplitIoClient
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def valid_split_parameters(split_name)
|
27
|
-
valid_split_name?(
|
27
|
+
valid_split_name?(:split, split_name)
|
28
28
|
end
|
29
29
|
|
30
30
|
def valid_matcher_arguments(args)
|
@@ -68,7 +68,7 @@ module SplitIoClient
|
|
68
68
|
SplitIoClient.configuration.logger.error("#{method}: #{key} is too long - must be #{SplitIoClient.configuration.max_key_size} characters or less")
|
69
69
|
end
|
70
70
|
|
71
|
-
def valid_split_name?(
|
71
|
+
def valid_split_name?(method, split_name)
|
72
72
|
if split_name.nil?
|
73
73
|
log_nil(:split_name, method)
|
74
74
|
return false
|
@@ -87,62 +87,62 @@ module SplitIoClient
|
|
87
87
|
true
|
88
88
|
end
|
89
89
|
|
90
|
-
def valid_key?(key)
|
90
|
+
def valid_key?(method, key)
|
91
91
|
if key.nil?
|
92
|
-
log_nil(:key,
|
92
|
+
log_nil(:key, method)
|
93
93
|
return false
|
94
94
|
end
|
95
95
|
|
96
96
|
true
|
97
97
|
end
|
98
98
|
|
99
|
-
def valid_matching_key?(matching_key)
|
99
|
+
def valid_matching_key?(method, matching_key)
|
100
100
|
if matching_key.nil?
|
101
|
-
log_nil(:matching_key,
|
101
|
+
log_nil(:matching_key, method)
|
102
102
|
return false
|
103
103
|
end
|
104
104
|
|
105
105
|
unless number_or_string?(matching_key)
|
106
|
-
log_invalid_type(:matching_key,
|
106
|
+
log_invalid_type(:matching_key, method)
|
107
107
|
return false
|
108
108
|
end
|
109
109
|
|
110
110
|
if empty_string?(matching_key)
|
111
|
-
log_empty_string(:matching_key,
|
111
|
+
log_empty_string(:matching_key, method)
|
112
112
|
return false
|
113
113
|
end
|
114
114
|
|
115
|
-
log_convert_numeric(:matching_key,
|
115
|
+
log_convert_numeric(:matching_key, method, matching_key) if matching_key.is_a? Numeric
|
116
116
|
|
117
117
|
if matching_key.size > SplitIoClient.configuration.max_key_size
|
118
|
-
log_key_too_long(:matching_key,
|
118
|
+
log_key_too_long(:matching_key, method)
|
119
119
|
return false
|
120
120
|
end
|
121
121
|
|
122
122
|
true
|
123
123
|
end
|
124
124
|
|
125
|
-
def valid_bucketing_key?(key, bucketing_key)
|
125
|
+
def valid_bucketing_key?(method, key, bucketing_key)
|
126
126
|
if key.is_a? Hash
|
127
127
|
if bucketing_key.nil?
|
128
|
-
log_nil(:bucketing_key,
|
128
|
+
log_nil(:bucketing_key, method)
|
129
129
|
return false
|
130
130
|
end
|
131
131
|
|
132
132
|
unless number_or_string?(bucketing_key)
|
133
|
-
log_invalid_type(:bucketing_key,
|
133
|
+
log_invalid_type(:bucketing_key, method)
|
134
134
|
return false
|
135
135
|
end
|
136
136
|
|
137
137
|
if empty_string?(bucketing_key)
|
138
|
-
log_empty_string(:bucketing_key,
|
138
|
+
log_empty_string(:bucketing_key, method)
|
139
139
|
return false
|
140
140
|
end
|
141
141
|
|
142
|
-
log_convert_numeric(:bucketing_key,
|
142
|
+
log_convert_numeric(:bucketing_key, method, bucketing_key) if bucketing_key.is_a? Numeric
|
143
143
|
|
144
144
|
if bucketing_key.size > SplitIoClient.configuration.max_key_size
|
145
|
-
log_key_too_long(:bucketing_key,
|
145
|
+
log_key_too_long(:bucketing_key, method)
|
146
146
|
return false
|
147
147
|
end
|
148
148
|
end
|
@@ -150,18 +150,18 @@ module SplitIoClient
|
|
150
150
|
true
|
151
151
|
end
|
152
152
|
|
153
|
-
def valid_split_names?(split_names)
|
153
|
+
def valid_split_names?(method, split_names)
|
154
154
|
unless !split_names.nil? && split_names.is_a?(Array)
|
155
|
-
SplitIoClient.configuration.logger.error(
|
155
|
+
SplitIoClient.configuration.logger.error("#{method}: split_names must be a non-empty Array")
|
156
156
|
return false
|
157
157
|
end
|
158
158
|
|
159
159
|
true
|
160
160
|
end
|
161
161
|
|
162
|
-
def valid_attributes?(attributes)
|
162
|
+
def valid_attributes?(method, attributes)
|
163
163
|
unless attributes.nil? || attributes.is_a?(Hash)
|
164
|
-
SplitIoClient.configuration.logger.error(
|
164
|
+
SplitIoClient.configuration.logger.error("#{method}: attributes must be of type Hash")
|
165
165
|
return false
|
166
166
|
end
|
167
167
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: splitclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.3.0.pre.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Split Software
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: allocation_stats
|
@@ -402,9 +402,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
402
402
|
version: '0'
|
403
403
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
404
404
|
requirements:
|
405
|
-
- - "
|
405
|
+
- - ">"
|
406
406
|
- !ruby/object:Gem::Version
|
407
|
-
version:
|
407
|
+
version: 1.3.1
|
408
408
|
requirements: []
|
409
409
|
rubyforge_project:
|
410
410
|
rubygems_version: 2.7.6
|