splitclient-rb 3.1.3 → 3.2.0

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
  SHA1:
3
- metadata.gz: e5c4c6e0e239b150d86755ca6d3f5ae58aec3be6
4
- data.tar.gz: 559ebfa78216411b6aecbe31bd885a17d2ae8d24
3
+ metadata.gz: 8c70d0111affdbf6d68572303ca335f9712fc8b3
4
+ data.tar.gz: 58b5eacffa5a95c9ff1b906b376660e0d6331788
5
5
  SHA512:
6
- metadata.gz: 05fb963b7cec39bb32ab3fff03f5333fb3dde6a631af31c897c8552995f2d556d79ae977b1f0f03754021760a334bdd90d26b9886e065878b2cf955ff14ae2dd
7
- data.tar.gz: 0cbde59397c915e4967358798cff31fabb750f440d605895ac83a8a1206e74de22d1036f10c28b145acc75d620e3ab51762a99462ae3630991113a4b242c7a96
6
+ metadata.gz: 99550497b640c1fdfc623ecf1800dea0e4d4ca6837132b81fd9c1ddd302bce2e251976fc46897670fe9ba87a1d62a1ba1a3fc4a385df43e90cbe3d1e86ada374
7
+ data.tar.gz: d1e7b4822aae284c209b2b866a61c4e8cfc95a71204c5b9ff96fa91c11884fa6bae83342571e81495b4b29a24610ee7d3f33fc4fe845f56a87277fd6a2c7a664
data/CHANGES.txt CHANGED
@@ -1,3 +1,6 @@
1
+ 3.2.0
2
+ - Add impression labeling
3
+
1
4
  3.1.3
2
5
  - Refactor SplitFactory - split it into separate mangers and client classes
3
6
  - Refactor Utilities to comply style guide
data/Detailed-README.md CHANGED
@@ -152,9 +152,13 @@ split_client.get_treatment('user_id','feature_name', attr: 'val')
152
152
 
153
153
  *default value* = `Logger.new($stdout)`
154
154
 
155
- **block_until_ready** : The SDK will block your app for provided amount of seconds until it's ready. If timeout expires `SplitIoClient::SDKBlockerTimeoutExpiredException` will be thrown. If `false` provided, then SDK would run in non-blocking mode
155
+ **ready** : The SDK will block your app for provided amount of seconds until it's ready. If timeout expires `SplitIoClient::SDKBlockerTimeoutExpiredException` will be thrown. If `0` provided, then SDK would run in non-blocking mode
156
156
 
157
- *default value* = `false`
157
+ *default value* = `0`
158
+
159
+ **labels_enabled** : Enables sending labels along with sensitive information
160
+
161
+ *default value* = `true`
158
162
 
159
163
  **mode** : See [SDK modes section](#sdk-modes).
160
164
 
@@ -211,7 +215,7 @@ end
211
215
 
212
216
  #### IMPORTANT
213
217
 
214
- For now, SDK does not support both `producer` mode and `block_until_ready`. You must either run SDK in `standalone` mode, or do not use `block_until_ready` option.
218
+ For now, SDK does not support both `producer` mode and `ready`. You must either run SDK in `standalone` mode, or do not use `ready` option.
215
219
 
216
220
  This begin-rescue-end block is optional, you might want to use it to catch timeout expired exception and apply some logic.
217
221
 
@@ -367,7 +371,7 @@ To run the suite of unit tests a rake task is provided.
367
371
 
368
372
  Make sure redis is running in localhost at redis://127.0.0.1:6379/0 and then just run:
369
373
  ```bash
370
- SPLITCLIENT_ENV=test bundle exec rspecrake spec
374
+ SPLITCLIENT_ENV=test bundle exec rspec spec
371
375
  ```
372
376
 
373
377
  Also, simplecov is used for coverage reporting. After the execution of the rake task it will create the `/coverage` folder with coverage reports in pretty HTML format.
data/console ADDED
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ bundle exec rake console
data/exe/splitio CHANGED
@@ -54,8 +54,8 @@ opt_parser = OptionParser.new do |opts|
54
54
  options[:impressions_refresh_rate] = c
55
55
  end
56
56
 
57
- opts.on("--block-until-ready=SECONDS", "Seconds to block the app until SDK is ready or false to run in non-blocking mode") do |c|
58
- options[:block_until_ready] = c
57
+ opts.on("--ready=SECONDS", "Seconds to block the app until SDK is ready or false to run in non-blocking mode") do |c|
58
+ options[:ready] = c
59
59
  end
60
60
 
61
61
  opts.on("--redis-url=REDIS_URL", "Set base uri for Split SDK") do |c|
@@ -5,6 +5,8 @@ module SplitIoClient
5
5
  module Adapters
6
6
  # Redis adapter used to provide interface to Redis
7
7
  class RedisAdapter
8
+ SCAN_SLICE = 5000
9
+
8
10
  attr_reader :redis
9
11
 
10
12
  def initialize(redis_url)
@@ -52,7 +54,17 @@ module SplitIoClient
52
54
  end
53
55
 
54
56
  def find_strings_by_prefix(prefix)
55
- @redis.keys("#{prefix}*")
57
+ memo = { items: [], cursor: 0 }
58
+
59
+ loop do
60
+ memo[:cursor], items = @redis.scan(memo[:cursor], match: "#{prefix}*", count: SCAN_SLICE)
61
+
62
+ memo[:items].push(*items)
63
+
64
+ break if memo[:cursor] == '0'
65
+ end
66
+
67
+ memo[:items]
56
68
  end
57
69
 
58
70
  def multiple_strings(keys)
@@ -18,13 +18,15 @@ module SplitIoClient
18
18
  end
19
19
  end
20
20
 
21
- def add_bulk(key, bucketing_key, treatments, time)
22
- treatments.each do |split_name, treatment|
21
+ def add_bulk(key, bucketing_key, treatments_labels_change_numbers, time)
22
+ treatments_labels_change_numbers.each do |split_name, treatment_label_number|
23
23
  add(
24
24
  split_name,
25
25
  'key_name' => key,
26
26
  'bucketing_key' => bucketing_key,
27
- 'treatment' => treatment,
27
+ 'treatment' => treatment_label_number[:treatment],
28
+ 'label' => @config.labels_enabled ? treatment_label_number[:label] : nil,
29
+ 'change_number' => treatment_label_number[:change_number],
28
30
  'time' => time
29
31
  )
30
32
  end
@@ -32,7 +34,7 @@ module SplitIoClient
32
34
 
33
35
  # Get everything from the queue and leave it empty
34
36
  def clear
35
- @adapter.clear
37
+ @adapter.clear.map { |impression| impression.update(ip: @config.machine_ip) }
36
38
  end
37
39
 
38
40
  private
@@ -13,15 +13,23 @@ module SplitIoClient
13
13
  # Store impression data in Redis
14
14
  def add(split_name, data)
15
15
  @adapter.add_to_set(
16
- namespace_key("impressions.#{split_name}"), data.merge(split_name: split_name).to_json
16
+ namespace_key("impressions.#{split_name}"),
17
+ data.merge(split_name: split_name).to_json
17
18
  )
18
19
  end
19
20
 
20
- def add_bulk(key, bucketing_key, treatments, time)
21
+ def add_bulk(key, bucketing_key, treatments_labels_change_numbers, time)
21
22
  @adapter.redis.pipelined do
22
- treatments.each_slice(IMPRESSIONS_SLICE) do |treatments_slice|
23
- treatments_slice.each do |split_name, treatment|
24
- add(split_name, 'key_name' => key, 'bucketing_key' => bucketing_key, 'treatment' => treatment, 'time' => time)
23
+ treatments_labels_change_numbers.each_slice(IMPRESSIONS_SLICE) do |treatments_labels_change_numbers_slice|
24
+ treatments_labels_change_numbers_slice.each do |split_name, treatment_label_change_number|
25
+ add(split_name,
26
+ 'key_name' => key,
27
+ 'bucketing_key' => bucketing_key,
28
+ 'treatment' => treatment_label_change_number[:treatment],
29
+ 'label' => @config.labels_enabled ? treatment_label_change_number[:label] : nil,
30
+ 'change_number' => treatment_label_change_number[:change_number],
31
+ 'time' => time
32
+ )
25
33
  end
26
34
  end
27
35
  end
@@ -31,13 +39,15 @@ module SplitIoClient
31
39
  # delete fetched impressions afterwards
32
40
  def clear
33
41
  impressions = impression_keys.each_with_object([]) do |key, memo|
42
+ _, _, ip, = key.split('/')
34
43
  members = @adapter.random_set_elements(key, @config.impressions_queue_size)
35
44
  members.each do |impression|
36
45
  parsed_impression = JSON.parse(impression)
37
46
 
38
47
  memo << {
39
48
  feature: parsed_impression['split_name'],
40
- impressions: parsed_impression.reject { |k, _| k == 'split_name' }
49
+ impressions: parsed_impression.reject { |k| k == 'split_name' },
50
+ ip: ip
41
51
  }
42
52
  end
43
53
 
@@ -51,7 +61,7 @@ module SplitIoClient
51
61
 
52
62
  # Get all sets by prefix
53
63
  def impression_keys
54
- @adapter.find_sets_by_prefix(namespace_key('impressions.'))
64
+ @adapter.find_sets_by_prefix("#{@config.redis_namespace}/*/impressions.*")
55
65
  end
56
66
  end
57
67
  end
@@ -12,7 +12,7 @@ module SplitIoClient
12
12
  protected
13
13
 
14
14
  def namespace_key(key)
15
- "#{@config.redis_namespace}/#{key}"
15
+ "#{@config.redis_namespace}/#{@config.machine_ip}/#{key}"
16
16
  end
17
17
  end
18
18
  end
@@ -14,20 +14,26 @@ module SplitIoClient
14
14
  return [] if impressions.empty? || filtered_impressions.empty?
15
15
 
16
16
  formatted_impressions = unique_features(filtered_impressions).each_with_object([]) do |feature, memo|
17
+ ip = nil
17
18
  current_impressions =
18
19
  filtered_impressions
19
- .select { |i| i[:feature] == feature }
20
- .map do |i|
20
+ .select { |impression| impression[:feature] == feature }
21
+ .map do |impression|
22
+ ip = impression[:ip]
21
23
  {
22
- keyName: i[:impressions]['key_name'],
23
- treatment: i[:impressions]['treatment'],
24
- time: i[:impressions]['time']
24
+ keyName: impression[:impressions]['key_name'],
25
+ treatment: impression[:impressions]['treatment'],
26
+ time: impression[:impressions]['time'],
27
+ bucketingKey: impression[:impressions]['bucketing_key'],
28
+ label: impression[:impressions]['label'],
29
+ changeNumber: impression[:impressions]['change_number'],
25
30
  }
26
31
  end
27
32
 
28
33
  memo << {
29
34
  testName: feature,
30
- keyImpressions: current_impressions
35
+ keyImpressions: current_impressions,
36
+ ip: ip
31
37
  }
32
38
  end
33
39
 
@@ -37,7 +43,7 @@ module SplitIoClient
37
43
  private
38
44
 
39
45
  def unique_features(impressions)
40
- impressions.map { |i| i[:feature] }.uniq
46
+ impressions.map { |impression| impression[:feature] }.uniq
41
47
  end
42
48
 
43
49
  # Filter seen impressions by impression_hash
@@ -57,6 +63,8 @@ module SplitIoClient
57
63
  def impression_hash(impression)
58
64
  "#{impression[:feature]}:" \
59
65
  "#{impression[:impressions]['key_name']}:" \
66
+ "#{impression[:impressions]['bucketing_key']}:" \
67
+ "#{impression[:impressions]['change_number']}:" \
60
68
  "#{impression[:impressions]['treatment']}"
61
69
  end
62
70
  end
@@ -50,7 +50,7 @@ module SplitIoClient
50
50
 
51
51
  @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
52
52
 
53
- if @config.block_until_ready && !@sdk_blocker.ready?
53
+ if @config.block_until_ready > 0 && !@sdk_blocker.ready?
54
54
  @sdk_blocker.splits_ready!
55
55
  @config.logger.info('splits are ready')
56
56
  end
@@ -15,12 +15,16 @@ module SplitIoClient
15
15
  config.logger.debug("GET #{url}") if config.debug_enabled
16
16
  end
17
17
  rescue StandardError => e
18
- config.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
18
+ config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
19
+
20
+ false
19
21
  end
20
22
 
21
- def post_api(url, config, api_key, data, params = {})
23
+ def post_api(url, config, api_key, data, headers = {}, params = {})
22
24
  api_client.post(url) do |req|
23
- req.headers = common_headers(api_key, config).merge('Content-Type' => 'application/json')
25
+ req.headers = common_headers(api_key, config)
26
+ .merge('Content-Type' => 'application/json')
27
+ .merge(headers)
24
28
 
25
29
  req.body = data.to_json
26
30
 
@@ -35,6 +39,8 @@ module SplitIoClient
35
39
  end
36
40
  rescue StandardError => e
37
41
  config.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
42
+
43
+ false
38
44
  end
39
45
 
40
46
  private
@@ -13,12 +13,14 @@ module SplitIoClient
13
13
  return
14
14
  end
15
15
 
16
- result = post_api("#{@config.events_uri}/testImpressions/bulk", @config, @api_key, @impressions)
16
+ impressions_by_ip.each do |ip, impressions|
17
+ result = post_api("#{@config.events_uri}/testImpressions/bulk", @config, @api_key, impressions, 'SplitSDKMachineIP' => ip)
17
18
 
18
- if result.status / 100 != 2
19
- @config.logger.error("Unexpected status code while posting impressions: #{result.status}")
20
- else
21
- @config.logger.debug("Impressions reported: #{total_impressions(@impressions)}") if @config.debug_enabled
19
+ if (200..299).include? result.status
20
+ @config.logger.debug("Impressions reported: #{total_impressions(@impressions)}") if @config.debug_enabled
21
+ else
22
+ @config.logger.error("Unexpected status code while posting impressions: #{result.status}")
23
+ end
22
24
  end
23
25
  end
24
26
 
@@ -29,6 +31,12 @@ module SplitIoClient
29
31
  impressions_count += impression[:keyImpressions].length
30
32
  end
31
33
  end
34
+
35
+ private
36
+
37
+ def impressions_by_ip
38
+ @impressions.group_by { |impression| impression[:ip] }
39
+ end
32
40
  end
33
41
  end
34
42
  end
@@ -23,11 +23,7 @@ module SplitIoClient
23
23
 
24
24
  result = post_api("#{@config.events_uri}/metrics/time", @config, @api_key, metrics_time)
25
25
 
26
- if result.status / 100 != 2
27
- @config.logger.error("Unexpected status code while posting time metrics: #{result.status}")
28
- else
29
- @config.logger.debug("Metric time reported: #{metrics_time.size}") if @config.debug_enabled
30
- end
26
+ log_status(result, metrics_time.size)
31
27
  end
32
28
  end
33
29
 
@@ -43,15 +39,23 @@ module SplitIoClient
43
39
 
44
40
  result = post_api("#{@config.events_uri}/metrics/counter", @config, @api_key, metrics_count)
45
41
 
46
- if result.status / 100 != 2
47
- @config.logger.error("Unexpected status code while posting count metrics: #{result.status}")
48
- else
49
- @config.logger.debug("Metric counts reported: #{metrics_count.size}") if @config.debug_enabled
50
- end
42
+ log_status(result, metrics_count.size)
51
43
  end
52
44
  end
53
45
  @metrics_repository.clear_counts
54
46
  end
47
+
48
+ private
49
+
50
+ def log_status(result, info_to_log)
51
+ if result == false
52
+ @config.logger.error("Failed to make a http request")
53
+ elsif (200..299).include? result.status
54
+ @config.logger.debug("Metric time reported: #{info_to_log}") if @config.debug_enabled
55
+ else
56
+ @config.logger.error("Unexpected status code while posting time metrics: #{result.status}")
57
+ end
58
+ end
55
59
  end
56
60
  end
57
61
  end
@@ -35,7 +35,9 @@ module SplitIoClient
35
35
  segments = []
36
36
  segment = get_api("#{@config.base_uri}/segmentChanges/#{name}", @config, @api_key, since: since)
37
37
 
38
- if segment.status / 100 == 2
38
+ if segment == false
39
+ @config.logger.error("Failed to make a http request")
40
+ elsif segment.status / 100 == 2
39
41
  segment_content = JSON.parse(segment.body, symbolize_names: true)
40
42
  @segments_repository.set_change_number(name, segment_content[:till])
41
43
  @metrics.count(prefix + '.status.' + segment.status.to_s, 1)
@@ -12,7 +12,9 @@ module SplitIoClient
12
12
  prefix = 'splitChangeFetcher'
13
13
  splits = get_api("#{@config.base_uri}/splitChanges", @config, @api_key, since: since)
14
14
 
15
- if splits.status / 100 == 2
15
+ if splits == false
16
+ @config.logger.error("Failed to make a http request")
17
+ elsif splits.status / 100 == 2
16
18
  result = splits_with_segment_names(splits.body)
17
19
 
18
20
  @metrics.count(prefix + '.status.' + splits.status.to_s, 1)
@@ -0,0 +1,12 @@
1
+ module SplitIoClient
2
+ module Engine
3
+ module Models
4
+ class Label
5
+ ARCHIVED = 'archived'
6
+ NO_RULE_MATCHED = 'no rule matched'
7
+ EXCEPTION = 'exception'
8
+ KILLED = 'killed'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,6 +1,9 @@
1
1
  require 'json'
2
2
  require 'thread'
3
3
 
4
+ include SplitIoClient::Cache::Stores
5
+ include SplitIoClient::Cache::Senders
6
+
4
7
  module SplitIoClient
5
8
  #
6
9
  # acts as an api adapater to connect to split endpoints
@@ -56,22 +59,22 @@ module SplitIoClient
56
59
 
57
60
  # Starts thread which loops constantly and stores splits in the splits_repository of choice
58
61
  def split_store
59
- SplitIoClient::Cache::Stores::SplitStore.new(@splits_repository, @config, @api_key, @metrics, @sdk_blocker).call
62
+ SplitStore.new(@splits_repository, @config, @api_key, @metrics, @sdk_blocker).call
60
63
  end
61
64
 
62
65
  # Starts thread which loops constantly and stores segments in the segments_repository of choice
63
66
  def segment_store
64
- SplitIoClient::Cache::Stores::SegmentStore.new(@segments_repository, @config, @api_key, @metrics, @sdk_blocker).call
67
+ SegmentStore.new(@segments_repository, @config, @api_key, @metrics, @sdk_blocker).call
65
68
  end
66
69
 
67
70
  # Starts thread which loops constantly and sends impressions to the Split API
68
71
  def impressions_sender
69
- SplitIoClient::Cache::Senders::ImpressionsSender.new(@impressions_repository, @config, @api_key).call
72
+ ImpressionsSender.new(@impressions_repository, @config, @api_key).call
70
73
  end
71
74
 
72
75
  # Starts thread which loops constantly and sends metrics to the Split API
73
76
  def metrics_sender
74
- SplitIoClient::Cache::Senders::MetricsSender.new(@metrics_repository, @config, @api_key).call
77
+ MetricsSender.new(@metrics_repository, @config, @api_key).call
75
78
  end
76
79
  end
77
80
  end
@@ -8,29 +8,38 @@ module SplitIoClient
8
8
 
9
9
  def call(keys, split, attributes = nil)
10
10
  split_model = Models::Split.new(split)
11
- default_treatment = split[:defaultTreatment]
11
+ @default_treatment = split[:defaultTreatment]
12
12
 
13
- return Treatments::CONTROL if split_model.archived?
13
+ return treatment(Models::Label::ARCHIVED, Treatments::CONTROL, split[:changeNumber]) if split_model.archived?
14
14
 
15
- split_model.matchable? ? match(split, keys, attributes, default_treatment) : default_treatment
15
+ if split_model.matchable?
16
+ match(split, keys, attributes)
17
+ else
18
+ treatment(Models::Label::KILLED, @default_treatment, split[:changeNumber])
19
+ end
16
20
  end
17
21
 
18
22
  private
19
23
 
20
- def match(split, keys, attributes, default_treatment)
24
+ def match(split, keys, attributes)
21
25
  split[:conditions].each do |c|
22
26
  condition = SplitIoClient::Condition.new(c)
23
27
 
24
28
  next if condition.empty?
25
29
 
26
30
  if matcher_type(condition).match?(keys[:matching_key], attributes)
27
- treatment = Splitter.get_treatment(keys[:bucketing_key], split[:seed], condition.partitions)
31
+ key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
32
+ result = Splitter.get_treatment(key, split[:seed], condition.partitions)
28
33
 
29
- return treatment.nil? ? default_treatment : treatment
34
+ if result.nil?
35
+ return treatment(Models::Label::NO_RULE_MATCHED, @default_treatment, split[:changeNumber])
36
+ else
37
+ return treatment(c[:label], result, split[:changeNumber])
38
+ end
30
39
  end
31
40
  end
32
41
 
33
- default_treatment
42
+ treatment(Models::Label::NO_RULE_MATCHED, @default_treatment, split[:changeNumber])
34
43
  end
35
44
 
36
45
  def matcher_type(condition)
@@ -53,6 +62,10 @@ module SplitIoClient
53
62
  final_matcher
54
63
  end
55
64
  end
65
+
66
+ def treatment(label, treatment, change_number = nil)
67
+ { label: label, treatment: treatment, change_number: change_number }
68
+ end
56
69
  end
57
70
  end
58
71
  end
@@ -56,4 +56,5 @@ require 'engine/evaluator/splitter'
56
56
  require 'engine/metrics/metrics'
57
57
  require 'engine/metrics/binary_search_latency_tracker'
58
58
  require 'engine/models/split'
59
+ require 'engine/models/label'
59
60
  require 'splitclient-rb_utilitites'
@@ -1,114 +1,136 @@
1
1
  module SplitIoClient
2
2
  class SplitClient
3
- #
4
- # Creates a new split client instance that connects to split.io API.
5
- #
6
- # @param api_key [String] the API key for your split account
7
- #
8
- # @return [SplitIoClient] split.io client instance
9
- def initialize(api_key, config = {}, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository)
10
- @config = config
11
-
12
- @splits_repository = splits_repository
13
- @segments_repository = segments_repository
14
- @impressions_repository = impressions_repository
15
- @metrics_repository = metrics_repository
16
-
17
- @adapter = adapter
18
- end
19
3
 
20
- def get_treatments(key, split_names, attributes = nil)
21
- bucketing_key, matching_key = keys_from_key(key)
22
- bucketing_key = matching_key if bucketing_key.nil?
4
+ #
5
+ # Creates a new split client instance that connects to split.io API.
6
+ #
7
+ # @param api_key [String] the API key for your split account
8
+ #
9
+ # @return [SplitIoClient] split.io client instance
10
+ def initialize(api_key, config = {}, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository)
11
+ @config = config
12
+
13
+ @splits_repository = splits_repository
14
+ @segments_repository = segments_repository
15
+ @impressions_repository = impressions_repository
16
+ @metrics_repository = metrics_repository
17
+
18
+ @adapter = adapter
19
+ end
20
+
21
+ def get_treatments(key, split_names, attributes = nil)
22
+ bucketing_key, matching_key = keys_from_key(key)
23
23
 
24
- treatments =
25
- @splits_repository.get_splits(split_names).each_with_object({}) do |(name, data), memo|
26
- memo.merge!(name => get_treatment(key, name, attributes, data, false))
24
+ treatments_labels_change_numbers =
25
+ @splits_repository.get_splits(split_names).each_with_object({}) do |(name, data), memo|
26
+ memo.merge!(name => get_treatment(key, name, attributes, data, false, true))
27
+ end
28
+
29
+ if @config.impressions_queue_size > 0
30
+ @impressions_repository.add_bulk(matching_key, bucketing_key, treatments_labels_change_numbers, (Time.now.to_f * 1000.0).to_i)
27
31
  end
28
32
 
29
- if @config.impressions_queue_size > 0
30
- @impressions_repository.add_bulk(matching_key, bucketing_key, treatments, (Time.now.to_f * 1000.0).to_i)
33
+ split_names = treatments_labels_change_numbers.keys
34
+ treatments = treatments_labels_change_numbers.values.map { |v| v[:treatment] }
35
+
36
+ Hash[split_names.zip(treatments)]
31
37
  end
32
38
 
33
- treatments
34
- end
39
+ #
40
+ # obtains the treatment for a given feature
41
+ #
42
+ # @param key [String/Hash] user id or hash with matching_key/bucketing_key
43
+ # @param split_name [String/Array] name of the feature that is being validated or array of them
44
+ # @param attributes [Hash] attributes to pass to the treatment class
45
+ # @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
46
+ # @param store_impressions [Boolean] impressions aren't stored if this flag is false
47
+ # @param multiple [Hash] internal flag to signal if method is called by get_treatments
48
+ #
49
+ # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
50
+ def get_treatment(key, split_name, attributes = nil, split_data = nil, store_impressions = true, multiple = false)
51
+ bucketing_key, matching_key = keys_from_key(key)
52
+
53
+ if matching_key.nil?
54
+ @config.logger.warn('matching_key was null for split_name: ' + split_name.to_s)
55
+ return parsed_treatment(multiple, { label: Engine::Models::Label::EXCEPTION, treatment: Treatments::CONTROL })
56
+ end
35
57
 
36
- #
37
- # obtains the treatment for a given feature
38
- #
39
- # @param key [String/Hash] user id or hash with matching_key/bucketing_key
40
- # @param split_name [String/Array] name of the feature that is being validated or array of them
41
- #
42
- # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
43
- def get_treatment(key, split_name, attributes = nil, split_data = nil, store_impressions = true)
44
- bucketing_key, matching_key = keys_from_key(key)
45
- bucketing_key = matching_key if bucketing_key.nil?
46
-
47
- if matching_key.nil?
48
- @config.logger.warn('matching_key was null for split_name: ' + split_name.to_s)
49
- return Treatments::CONTROL
50
- end
58
+ if split_name.nil?
59
+ @config.logger.warn('split_name was null for key: ' + key)
60
+ return parsed_treatment(multiple, { label: Engine::Models::Label::EXCEPTION, treatment: Treatments::CONTROL })
61
+ end
51
62
 
52
- if split_name.nil?
53
- @config.logger.warn('split_name was null for key: ' + key)
54
- return Treatments::CONTROL
55
- end
63
+ start = Time.now
64
+ treatment_label_change_number = { label: Engine::Models::Label::EXCEPTION, treatment: Treatments::CONTROL }
56
65
 
57
- start = Time.now
58
- result = nil
66
+ begin
67
+ split = multiple ? split_data : @splits_repository.get_split(split_name)
59
68
 
60
- begin
61
- split = split_data ? split_data : @splits_repository.get_split(split_name)
69
+ if split.nil?
70
+ return parsed_treatment(multiple, treatment_label_change_number)
71
+ else
72
+ treatment_label_change_number = SplitIoClient::Engine::Parser::SplitTreatment.new(@segments_repository).call(
73
+ { bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
74
+ )
75
+ end
76
+ rescue StandardError => error
77
+ @config.log_found_exception(__method__.to_s, error)
62
78
 
63
- result = if split.nil?
64
- Treatments::CONTROL
65
- else
66
- SplitIoClient::Engine::Parser::SplitTreatment.new(@segments_repository).call(
67
- { bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
68
- )
79
+ return parsed_treatment(multiple, treatment_label_change_number)
69
80
  end
70
- rescue StandardError => error
71
- @config.log_found_exception(__method__.to_s, error)
72
- end
73
81
 
74
- result = result.nil? ? Treatments::CONTROL : result
75
-
76
- begin
77
- latency = (Time.now - start) * 1000.0
78
- if @config.impressions_queue_size > 0 && store_impressions
79
- # Disable impressions if @config.impressions_queue_size == -1
80
- @impressions_repository.add(split_name,
81
- 'key_name' => matching_key,
82
- 'bucketing_key' => bucketing_key,
83
- 'treatment' => result,
84
- 'time' => (Time.now.to_f * 1000.0).to_i
85
- )
82
+ begin
83
+ latency = (Time.now - start) * 1000.0
84
+ if @config.impressions_queue_size > 0 && store_impressions && split
85
+ # Disable impressions if @config.impressions_queue_size == -1
86
+ @impressions_repository.add(split_name,
87
+ 'key_name' => matching_key,
88
+ 'bucketing_key' => bucketing_key,
89
+ 'treatment' => treatment_label_change_number[:treatment],
90
+ 'label' => @config.labels_enabled ? treatment_label_change_number[:label] : nil,
91
+ 'time' => (Time.now.to_f * 1000.0).to_i,
92
+ 'change_number' => treatment_label_change_number[:change_number]
93
+ )
94
+ end
95
+
96
+ # Measure
97
+ @adapter.metrics.time('sdk.get_treatment', latency)
98
+ rescue StandardError => error
99
+ @config.log_found_exception(__method__.to_s, error)
100
+
101
+ return parsed_treatment(multiple, treatment_label_change_number)
86
102
  end
87
103
 
88
- # Measure
89
- @adapter.metrics.time("sdk.get_treatment", latency)
90
- rescue StandardError => error
91
- @config.log_found_exception(__method__.to_s, error)
104
+ parsed_treatment(multiple, treatment_label_change_number)
92
105
  end
93
106
 
94
- result
95
- end
107
+ def keys_from_key(key)
108
+ case key.class.to_s
109
+ when 'Hash'
110
+ key.values_at(:bucketing_key, :matching_key)
111
+ when 'String'
112
+ [nil, key]
113
+ end
114
+ end
96
115
 
97
- def keys_from_key(key)
98
- case key.class.to_s
99
- when 'Hash'
100
- key.values_at(:bucketing_key, :matching_key)
101
- when 'String'
102
- [key, key]
116
+ def parsed_treatment(multiple, treatment_label_change_number)
117
+ if multiple
118
+ {
119
+ treatment: treatment_label_change_number[:treatment],
120
+ label: treatment_label_change_number[:label],
121
+ change_number: treatment_label_change_number[:change_number]
122
+ }
123
+ else
124
+ treatment_label_change_number[:treatment]
125
+ end
103
126
  end
104
- end
105
127
 
106
- #
107
- # method that returns the sdk gem version
108
- #
109
- # @return [string] version value for this sdk
110
- def self.sdk_version
111
- 'ruby-'+SplitIoClient::VERSION
128
+ #
129
+ # method that returns the sdk gem version
130
+ #
131
+ # @return [string] version value for this sdk
132
+ def self.sdk_version
133
+ 'ruby-'+SplitIoClient::VERSION
134
+ end
112
135
  end
113
136
  end
114
- end
@@ -52,10 +52,12 @@ module SplitIoClient
52
52
  @logger = opts[:logger] || SplitConfig.default_logger
53
53
  @debug_enabled = opts[:debug_enabled] || SplitConfig.default_debug
54
54
  @transport_debug_enabled = opts[:transport_debug_enabled] || SplitConfig.default_debug
55
- @block_until_ready = opts[:block_until_ready] || false
55
+ @block_until_ready = opts[:ready] || opts[:block_until_ready] || 0
56
56
  @machine_name = SplitConfig.get_hostname
57
57
  @machine_ip = SplitConfig.get_ip
58
58
 
59
+ @labels_enabled = opts[:labels_enabled].nil? ? SplitConfig.default_labels_logging : opts[:labels_enabled]
60
+
59
61
  startup_log
60
62
  end
61
63
 
@@ -125,6 +127,12 @@ module SplitIoClient
125
127
  # @return [Boolean] The value for the debug flag
126
128
  attr_reader :transport_debug_enabled
127
129
 
130
+ #
131
+ # Enable logging labels and sending potentially sensitive information
132
+ #
133
+ # @return [Boolean] The value for the labels enabled flag
134
+ attr_reader :labels_enabled
135
+
128
136
  #
129
137
  # The number of seconds to wait for SDK readiness
130
138
  # or false to disable waiting
@@ -161,7 +169,7 @@ module SplitIoClient
161
169
  #
162
170
  # @return [string] version value for this sdk
163
171
  def self.sdk_version
164
- 'RubyClientSDK-'+SplitIoClient::VERSION
172
+ 'ruby-'+SplitIoClient::VERSION
165
173
  end
166
174
 
167
175
  #
@@ -261,6 +269,14 @@ module SplitIoClient
261
269
  false
262
270
  end
263
271
 
272
+ #
273
+ # The default labels logging value
274
+ #
275
+ # @return [boolean]
276
+ def self.default_labels_logging
277
+ true
278
+ end
279
+
264
280
  def self.default_redis_url
265
281
  'redis://127.0.0.1:6379/0'
266
282
  end
@@ -316,12 +332,9 @@ module SplitIoClient
316
332
  #
317
333
  # @return [string]
318
334
  def self.get_ip
319
- begin
320
- Socket::getaddrinfo(Socket.gethostname, 'echo', Socket::AF_INET)[0][3]
321
- rescue
322
- #unable to get local ip
323
- '127.0.0.0'
324
- end
335
+ Socket.ip_address_list.detect { |intf| intf.ipv4_private? }.ip_address
336
+ rescue StandardError
337
+ 'unknown'
325
338
  end
326
339
  end
327
340
  end
@@ -22,7 +22,7 @@ module SplitIoClient
22
22
  @client = SplitClient.new(@api_key, @config, @adapter, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository)
23
23
  @manager = SplitManager.new(@api_key, @config, @adapter, @splits_repository)
24
24
 
25
- @sdk_blocker.block if @config.block_until_ready
25
+ @sdk_blocker.block if @config.block_until_ready > 0
26
26
  end
27
27
  end
28
28
  end
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '3.1.3'
2
+ VERSION = '3.2.0'
3
3
  end
data/runTests ADDED
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ SPLITCLIENT_ENV=test bundle exec rspec spec
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: 3.1.3
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-04 00:00:00.000000000 Z
11
+ date: 2017-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -250,6 +250,7 @@ files:
250
250
  - NEWS
251
251
  - README.md
252
252
  - Rakefile
253
+ - console
253
254
  - exe/splitio
254
255
  - lib/cache/adapters/memory_adapter.rb
255
256
  - lib/cache/adapters/memory_adapters/map_adapter.rb
@@ -289,6 +290,7 @@ files:
289
290
  - lib/engine/matchers/whitelist_matcher.rb
290
291
  - lib/engine/metrics/binary_search_latency_tracker.rb
291
292
  - lib/engine/metrics/metrics.rb
293
+ - lib/engine/models/label.rb
292
294
  - lib/engine/models/split.rb
293
295
  - lib/engine/parser/condition.rb
294
296
  - lib/engine/parser/partition.rb
@@ -308,6 +310,7 @@ files:
308
310
  - lib/splitclient-rb/split_factory_builder.rb
309
311
  - lib/splitclient-rb/version.rb
310
312
  - lib/splitclient-rb_utilitites.rb
313
+ - runTests
311
314
  - splitclient-rb.gemspec
312
315
  - splitio.yml.example
313
316
  - tasks/benchmark_get_treatment.rake