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 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