splitclient-rb 6.2.0 → 6.3.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b65188c13bdff2ab3b11f0f313ace352a3eac9c9c82df2876b577f879eede39
4
- data.tar.gz: 1dac618f445a50911325d12e2cef429acb0b2b47f556e8ec8a2a79cc1a86f989
3
+ metadata.gz: 5b5098cf06d8181cb623522e84629c6df7cc454f7b9a636ec4f872935f73675a
4
+ data.tar.gz: 835d12570cfc5d16a227a8f39279ed71c874c0361cdb1bc4ee07979d168d3aa8
5
5
  SHA512:
6
- metadata.gz: 92cf25d901ecdab6e5d2f4cdc202a34b04afcfa14e3ab3215303be6d1903fc9c0d597285fa6f54e8fa4cdbfb3f4260d26e54bad5ae3923855d02b5ce18acbb9e
7
- data.tar.gz: 2ed3da2f6670579608805480ff0267b7a918945dae8a835df8866869280c41586928f22ea34e3b88fa12030587ed472c98d0e722eddb41835ea998f50b477577
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
- split_names.each_with_object({}) do |name, memo|
32
- memo.merge!(name => get_treatment(key, name, attributes))
33
- end
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 id [string] user id
40
- # @param feature [string] name of the feature that is being validated
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 [Treatment] treatment constant value
43
- def get_treatment(id, feature, attributes = nil)
44
- unless id
45
- SplitIoClient.configuration.logger.warn('id was null for feature: ' + feature)
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
- result = get_localhost_treatment(feature)
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
- # method to check the treatment for the given feature in localhost mode
83
- #
84
- # @return [boolean] true if the feature is available in localhost mode, false otherwise
85
- def get_localhost_treatment(feature)
86
- treatment = @localhost_mode_features.select { |h| h[:feature] == feature }.last || {}
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
- treatment[:treatment] || SplitIoClient::Engine::Models::Treatment::CONTROL
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
- control_treatment = { label: Engine::Models::Label::EXCEPTION, treatment: SplitIoClient::Engine::Models::Treatment::CONTROL }
78
- parsed_control_treatment = parsed_treatment(multiple, control_treatment)
79
-
80
- bucketing_key, matching_key = keys_from_key(key)
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
- evaluator ||= Engine::Parser::Evaluator.new(@segments_repository, @splits_repository)
94
-
95
- begin
96
- start = Time.now
97
-
98
- split = multiple ? split_data : @splits_repository.get_split(split_name)
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
- return parsed_control_treatment
121
- end
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
- parsed_treatment(multiple, treatment_data)
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.class.to_s
202
- when 'Hash'
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
- label: treatment_data[:label],
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('get_treatments: you passed an empty split_name, split_name must be a non-empty String or a Symbol')
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('get_treatments: you passed an invalid split_name, split_name must be a non-empty String or a Symbol')
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)
@@ -2,7 +2,7 @@ module SplitIoClient
2
2
  class LocalhostSplitFactory
3
3
  attr_reader :client, :manager
4
4
 
5
- def initialize(splits_file, reload_rate = nil)
5
+ def initialize(splits_file, reload_rate = nil, logger = nil)
6
6
  @splits_file = splits_file
7
7
  @reload_rate = reload_rate
8
8
 
@@ -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.find { |x| x[:feature] == split_name }
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 [object] array of splits
45
+ # @returns Array of split view
31
46
  def splits
32
- @localhost_mode_features
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.each_with_object([]) do |split, memo|
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
- splits_file = config[:path]? config[:path] : File.join(Dir.home, '.split')
8
+ SplitIoClient.configure( { logger: config[:logger] } )
9
9
 
10
- LocalhostSplitFactory.new(splits_file, config[:reload_rate])
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?(split_name, :split)
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?(split_name, method = :get_treatment)
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, :get_treatment)
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, :get_treatment)
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, :get_treatment)
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, :get_treatment)
111
+ log_empty_string(:matching_key, method)
112
112
  return false
113
113
  end
114
114
 
115
- log_convert_numeric(:matching_key, :get_treatment, matching_key) if matching_key.is_a? Numeric
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, :get_treatment)
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, :get_treatment)
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, :get_treatment)
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, :get_treatment)
138
+ log_empty_string(:bucketing_key, method)
139
139
  return false
140
140
  end
141
141
 
142
- log_convert_numeric(:bucketing_key, :get_treatment, bucketing_key) if bucketing_key.is_a? Numeric
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, :get_treatment)
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('get_treatments: split_names must be a non-empty Array')
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('get_treatment: attributes must be of type Hash')
164
+ SplitIoClient.configuration.logger.error("#{method}: attributes must be of type Hash")
165
165
  return false
166
166
  end
167
167
 
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '6.2.0'
2
+ VERSION = '6.3.0.pre.rc1'
3
3
  end
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.2.0
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-03-07 00:00:00.000000000 Z
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: '0'
407
+ version: 1.3.1
408
408
  requirements: []
409
409
  rubyforge_project:
410
410
  rubygems_version: 2.7.6