splitclient-rb 7.0.0.pre.rc3-java → 7.0.1-java

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +10 -0
  3. data/.gitignore +2 -0
  4. data/.travis.yml +9 -0
  5. data/CHANGES.txt +18 -11
  6. data/CONTRIBUTORS-GUIDE.md +49 -0
  7. data/README.md +75 -41
  8. data/lib/splitclient-rb.rb +4 -4
  9. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +4 -0
  10. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +4 -0
  11. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +4 -0
  12. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +2 -2
  13. data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +1 -1
  14. data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +2 -2
  15. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +0 -5
  16. data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +49 -0
  17. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +94 -0
  18. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +112 -0
  19. data/lib/splitclient-rb/cache/stores/segment_store.rb +1 -7
  20. data/lib/splitclient-rb/cache/stores/split_store.rb +1 -7
  21. data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
  22. data/lib/splitclient-rb/clients/split_client.rb +16 -13
  23. data/lib/splitclient-rb/engine/api/client.rb +6 -11
  24. data/lib/splitclient-rb/engine/api/events.rb +3 -5
  25. data/lib/splitclient-rb/engine/api/impressions.rb +1 -1
  26. data/lib/splitclient-rb/engine/parser/split_adapter.rb +18 -1
  27. data/lib/splitclient-rb/managers/split_manager.rb +10 -9
  28. data/lib/splitclient-rb/split_config.rb +79 -17
  29. data/lib/splitclient-rb/split_factory.rb +11 -4
  30. data/lib/splitclient-rb/split_factory_builder.rb +1 -21
  31. data/lib/splitclient-rb/version.rb +1 -1
  32. data/sonar-scanner.sh +42 -0
  33. metadata +11 -10
  34. data/Detailed-README.md +0 -576
  35. data/NEWS +0 -144
  36. data/lib/splitclient-rb/clients/localhost_split_client.rb +0 -184
  37. data/lib/splitclient-rb/localhost_split_factory.rb +0 -14
  38. data/lib/splitclient-rb/localhost_utils.rb +0 -59
  39. data/lib/splitclient-rb/managers/localhost_split_manager.rb +0 -60
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Stores
6
+ class LocalhostSplitBuilder
7
+ class << self
8
+ def build_splits(splits)
9
+ splits.map do |feature, treatments|
10
+ build_split(feature, treatments)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def build_split(feature, treatments)
17
+ {
18
+ name: feature,
19
+ status: 'ACTIVE',
20
+ killed: false,
21
+ trafficAllocation: 100,
22
+ seed: 2_089_907_429,
23
+ defaultTreatment: 'control_treatment',
24
+ configurations: build_configurations(treatments),
25
+ conditions: build_conditions(treatments)
26
+ }
27
+ end
28
+
29
+ def build_configurations(treatments)
30
+ treatments.reduce({}) do |hash, treatment|
31
+ hash.merge(treatment[:treatment].to_sym => treatment[:config])
32
+ end
33
+ end
34
+
35
+ def build_conditions(treatments)
36
+ conditions = treatments.map do |treatment|
37
+ if treatment[:keys]
38
+ build_whitelist_treatment(treatment[:treatment], Array(treatment[:keys]))
39
+ else
40
+ build_rollout_treatment
41
+ end
42
+ .merge(partitions: build_partitions(treatment[:treatment], treatments))
43
+ end
44
+
45
+ conditions.sort_by { |condition| condition[:conditionType] }.reverse!
46
+ end
47
+
48
+ def build_whitelist_treatment(treatment_name, whitelist_keys)
49
+ {
50
+ conditionType: 'WHITELIST',
51
+ matcherGroup: {
52
+ combiner: 'AND',
53
+ matchers: [{
54
+ keySelector: nil,
55
+ matcherType: 'WHITELIST',
56
+ negate: false,
57
+ whitelistMatcherData: {
58
+ whitelist: whitelist_keys
59
+ }
60
+ }]
61
+ },
62
+ label: "whitelisted #{treatment_name}"
63
+ }
64
+ end
65
+
66
+ def build_rollout_treatment
67
+ {
68
+ conditionType: 'ROLLOUT',
69
+ matcherGroup: {
70
+ combiner: 'AND',
71
+ matchers: [
72
+ {
73
+ matcherType: 'ALL_KEYS',
74
+ negate: false
75
+ }
76
+ ]
77
+ },
78
+ label: 'default rule'
79
+ }
80
+ end
81
+
82
+ def build_partitions(current_treatment_name, treatments)
83
+ treatments.map do |treatment|
84
+ {
85
+ treatment: treatment[:treatment],
86
+ size: treatment[:treatment] == current_treatment_name ? 100 : 0
87
+ }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Stores
6
+ class LocalhostSplitStore
7
+ attr_reader :splits_repository
8
+
9
+ def initialize(splits_repository, config, sdk_blocker = nil)
10
+ @splits_repository = splits_repository
11
+ @config = config
12
+ @sdk_blocker = sdk_blocker
13
+ end
14
+
15
+ def call
16
+ if ENV['SPLITCLIENT_ENV'] == 'test'
17
+ store_splits
18
+ else
19
+ splits_thread
20
+
21
+ if defined?(PhusionPassenger)
22
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
23
+ splits_thread if forked
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def splits_thread
32
+ @config.threads[:split_store] = Thread.new do
33
+ @config.logger.info('Starting splits fetcher service')
34
+ loop do
35
+ store_splits
36
+
37
+ sleep(StoreUtils.random_interval(@config.features_refresh_rate))
38
+ end
39
+ end
40
+ end
41
+
42
+ def store_splits
43
+ load_features.each do |split|
44
+ store_split(split)
45
+ end
46
+
47
+ if @sdk_blocker
48
+ @sdk_blocker.splits_ready!
49
+ @sdk_blocker.segments_ready!
50
+ end
51
+ rescue StandardError => error
52
+ @config.logger.error('Error while parsing the split file. ' \
53
+ 'Check that the input file matches the expected format')
54
+ @config.log_found_exception(__method__.to_s, error)
55
+ end
56
+
57
+ def store_split(split)
58
+ @config.logger.debug("storing split (#{split[:name]})") if @config.debug_enabled
59
+
60
+ @splits_repository.add_split(split)
61
+ end
62
+
63
+ def load_features
64
+ yaml_extensions = ['.yml', '.yaml']
65
+ if yaml_extensions.include? File.extname(@config.split_file)
66
+ parse_yaml_features
67
+ else
68
+ @config.logger.warn('Localhost mode: .split mocks ' \
69
+ 'will be deprecated soon in favor of YAML files, which provide more ' \
70
+ 'targeting power. Take a look in our documentation.')
71
+
72
+ parse_plain_text_features
73
+ end
74
+ end
75
+
76
+ def parse_plain_text_features
77
+ splits = File.open(@config.split_file).each_with_object({}) do |line, memo|
78
+ feature, treatment = line.strip.split(' ')
79
+
80
+ next if line.start_with?('#') || line.strip.empty?
81
+
82
+ memo[feature] = [{ treatment: treatment }]
83
+ end
84
+
85
+ LocalhostSplitBuilder.build_splits(splits)
86
+ end
87
+
88
+ def parse_yaml_features
89
+ splits = YAML.safe_load(File.read(@config.split_file)).each_with_object({}) do |feature, memo|
90
+ feat_symbolized_keys = symbolize_feat_keys(feature)
91
+
92
+ feat_name = feature.keys.first
93
+
94
+ if memo[feat_name]
95
+ memo[feat_name] << feat_symbolized_keys
96
+ else
97
+ memo[feat_name] = [feat_symbolized_keys]
98
+ end
99
+ end
100
+
101
+ LocalhostSplitBuilder.build_splits(splits)
102
+ end
103
+
104
+ def symbolize_feat_keys(yaml_feature)
105
+ yaml_feature.values.first.each_with_object({}) do |(k, v), memo|
106
+ memo[k.to_sym] = k == 'config' ? v.to_json : v
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -38,7 +38,7 @@ module SplitIoClient
38
38
  store_segments
39
39
  @config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
40
40
 
41
- sleep_for = random_interval(@config.segments_refresh_rate)
41
+ sleep_for = StoreUtils.random_interval(@config.segments_refresh_rate)
42
42
  @config.logger.debug("Segments store is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
43
43
  sleep(sleep_for)
44
44
  end
@@ -53,12 +53,6 @@ module SplitIoClient
53
53
  @config.log_found_exception(__method__.to_s, error)
54
54
  end
55
55
 
56
- def random_interval(interval)
57
- random_factor = Random.new.rand(50..100) / 100.0
58
-
59
- interval * random_factor
60
- end
61
-
62
56
  def segments_api
63
57
  @segments_api ||= SplitIoClient::Api::Segments.new(@api_key, @metrics, @segments_repository, @config)
64
58
  end
@@ -34,7 +34,7 @@ module SplitIoClient
34
34
  loop do
35
35
  store_splits
36
36
 
37
- sleep(random_interval(@config.features_refresh_rate))
37
+ sleep(StoreUtils.random_interval(@config.features_refresh_rate))
38
38
  end
39
39
  end
40
40
  end
@@ -57,12 +57,6 @@ module SplitIoClient
57
57
  @config.log_found_exception(__method__.to_s, error)
58
58
  end
59
59
 
60
- def random_interval(interval)
61
- random_factor = Random.new.rand(50..100) / 100.0
62
-
63
- interval * random_factor
64
- end
65
-
66
60
  def splits_since(since)
67
61
  splits_api.since(since)
68
62
  end
@@ -0,0 +1,13 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Stores
4
+ class StoreUtils
5
+ def self.random_interval(interval)
6
+ random_factor = Random.new.rand(50..100) / 100.0
7
+
8
+ interval * random_factor
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -74,11 +74,9 @@ module SplitIoClient
74
74
  @destroyed = true
75
75
  end
76
76
 
77
- def store_impression(split_name, matching_key, bucketing_key, treatment, store_impressions, attributes)
77
+ def store_impression(split_name, matching_key, bucketing_key, treatment, attributes)
78
78
  time = (Time.now.to_f * 1000.0).to_i
79
79
 
80
- return if @config.disable_impressions || !store_impressions
81
-
82
80
  @impressions_repository.add(
83
81
  matching_key,
84
82
  bucketing_key,
@@ -133,7 +131,7 @@ module SplitIoClient
133
131
  end
134
132
  end
135
133
 
136
- if ready? && !@splits_repository.traffic_type_exists(traffic_type_name)
134
+ if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name)
137
135
  @config.logger.warn("track: Traffic Type #{traffic_type_name} " \
138
136
  "does not have any corresponding Splits in this environment, make sure you're tracking " \
139
137
  'your events to a valid traffic type defined in the Split console')
@@ -249,14 +247,12 @@ module SplitIoClient
249
247
  # Measure
250
248
  @adapter.metrics.time('sdk.' + calling_method, latency)
251
249
 
252
- unless @config.disable_impressions
253
- time = (Time.now.to_f * 1000.0).to_i
254
- @impressions_repository.add_bulk(
255
- matching_key, bucketing_key, treatments_labels_change_numbers, time
256
- )
250
+ time = (Time.now.to_f * 1000.0).to_i
251
+ @impressions_repository.add_bulk(
252
+ matching_key, bucketing_key, treatments_labels_change_numbers, time
253
+ )
257
254
 
258
- route_impressions(sanitized_split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
259
- end
255
+ route_impressions(sanitized_split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
260
256
 
261
257
  split_names_keys = treatments_labels_change_numbers.keys
262
258
  treatments = treatments_labels_change_numbers.values.map do |v|
@@ -291,6 +287,8 @@ module SplitIoClient
291
287
 
292
288
  bucketing_key, matching_key = keys_from_key(key)
293
289
 
290
+ attributes = parsed_attributes(attributes)
291
+
294
292
  return parsed_control_exception unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, split_name, matching_key, bucketing_key, attributes)
295
293
 
296
294
  bucketing_key = bucketing_key ? bucketing_key.to_s : nil
@@ -328,14 +326,15 @@ module SplitIoClient
328
326
  end
329
327
 
330
328
  latency = (Time.now - start) * 1000.0
331
- store_impression(split_name, matching_key, bucketing_key, treatment_data, store_impressions, attributes)
329
+
330
+ store_impression(split_name, matching_key, bucketing_key, treatment_data, attributes) if store_impressions
332
331
 
333
332
  # Measure
334
333
  @adapter.metrics.time('sdk.' + calling_method, latency) unless multiple
335
334
  rescue StandardError => error
336
335
  @config.log_found_exception(__method__.to_s, error)
337
336
 
338
- store_impression(split_name, matching_key, bucketing_key, control_treatment, store_impressions, attributes)
337
+ store_impression(split_name, matching_key, bucketing_key, control_treatment, attributes) if store_impressions
339
338
 
340
339
  return parsed_control_exception
341
340
  end
@@ -351,5 +350,9 @@ module SplitIoClient
351
350
  return @sdk_blocker.ready? if @sdk_blocker
352
351
  true
353
352
  end
353
+
354
+ def parsed_attributes(attributes)
355
+ return attributes || attributes.to_h
356
+ end
354
357
  end
355
358
  end
@@ -30,6 +30,12 @@ module SplitIoClient
30
30
  req.headers = common_headers(api_key)
31
31
  .merge('Content-Type' => 'application/json')
32
32
  .merge(headers)
33
+
34
+ machine_ip = @config.machine_ip
35
+ machine_name = @config.machine_name
36
+
37
+ req.headers = req.headers.merge('SplitSDKMachineIP' => machine_ip) unless machine_ip.empty? || machine_ip == 'unknown'
38
+ req.headers = req.headers.merge('SplitSDKMachineName' => machine_name) unless machine_name.empty? || machine_name == 'unknown'
33
39
 
34
40
  req.body = data.to_json
35
41
 
@@ -78,19 +84,8 @@ module SplitIoClient
78
84
  {
79
85
  'Authorization' => "Bearer #{api_key}",
80
86
  'SplitSDKVersion' => "#{@config.language}-#{@config.version}",
81
- 'SplitSDKMachineName' => @config.machine_name,
82
- 'SplitSDKMachineIP' => @config.machine_ip,
83
- 'Referer' => referer
84
87
  }
85
88
  end
86
-
87
- def referer
88
- result = "#{@config.language}-#{@config.version}"
89
-
90
- result = "#{result}::#{SplitIoClient::SplitConfig.machine_hostname}" unless SplitIoClient::SplitConfig.machine_hostname == 'localhost'
91
-
92
- result
93
- end
94
89
  end
95
90
  end
96
91
  end
@@ -18,10 +18,7 @@ module SplitIoClient
18
18
  response = post_api(
19
19
  "#{@config.events_uri}/events/bulk",
20
20
  @api_key,
21
- events_slice.map { |event| formatted_event(event[:e]) },
22
- 'SplitSDKMachineIP' => events_slice[0][:m][:i],
23
- 'SplitSDKMachineName' => events_slice[0][:m][:n],
24
- 'SplitSDKVersion' => events_slice[0][:m][:s]
21
+ events_slice.map { |event| formatted_event(event[:e]) }
25
22
  )
26
23
 
27
24
  if response.success?
@@ -42,7 +39,8 @@ module SplitIoClient
42
39
  trafficTypeName: event[:trafficTypeName],
43
40
  eventTypeId: event[:eventTypeId],
44
41
  value: event[:value].to_f,
45
- timestamp: event[:timestamp].to_i
42
+ timestamp: event[:timestamp].to_i,
43
+ properties: event[:properties]
46
44
  }
47
45
  end
48
46
  end
@@ -15,7 +15,7 @@ module SplitIoClient
15
15
  end
16
16
 
17
17
  impressions_by_ip(impressions).each do |ip, impressions_ip|
18
- response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions_ip, 'SplitSDKMachineIP' => ip)
18
+ response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions_ip)
19
19
 
20
20
  if response.success?
21
21
  @config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
@@ -44,7 +44,9 @@ module SplitIoClient
44
44
  @sdk_blocker = sdk_blocker
45
45
  @config = config
46
46
 
47
- start_standalone_components if @config.standalone?
47
+ start_localhost_components if @config.localhost_mode
48
+
49
+ start_standalone_components if @config.standalone? && !@config.localhost_mode
48
50
  end
49
51
 
50
52
  def start_standalone_components
@@ -55,6 +57,21 @@ module SplitIoClient
55
57
  events_sender
56
58
  end
57
59
 
60
+ def start_localhost_components
61
+ localhost_split_store
62
+ localhost_repo_cleaner
63
+ end
64
+
65
+ # Starts thread which loops constantly and retrieves splits from a file source
66
+ def localhost_split_store
67
+ LocalhostSplitStore.new(@splits_repository, @config, @sdk_blocker).call
68
+ end
69
+
70
+ # Starts thread which loops constantly and cleans up repositories to avoid memory issues in localhost mode
71
+ def localhost_repo_cleaner
72
+ LocalhostRepoCleaner.new(@impressions_repository, @metrics_repository, @events_repository, @config).call
73
+ end
74
+
58
75
  # Starts thread which loops constantly and stores splits in the splits_repository of choice
59
76
  def split_store
60
77
  SplitStore.new(@splits_repository, @api_key, @metrics, @config, @sdk_blocker).call
@@ -3,13 +3,9 @@ module SplitIoClient
3
3
  #
4
4
  # Creates a new split manager instance that connects to split.io API.
5
5
  #
6
- # @param api_key [String] the API key for your split account
7
- #
8
6
  # @return [SplitIoManager] split.io client instance
9
- def initialize(api_key, adapter = nil, splits_repository = nil, sdk_blocker, config)
10
- @localhost_mode_features = []
7
+ def initialize(splits_repository = nil, sdk_blocker, config)
11
8
  @splits_repository = splits_repository
12
- @adapter = adapter
13
9
  @sdk_blocker = sdk_blocker
14
10
  @config = config
15
11
  end
@@ -91,13 +87,18 @@ module SplitIoClient
91
87
  return {} unless split
92
88
 
93
89
  begin
94
- treatments = split[:conditions]
95
- .detect { |c| c[:conditionType] == 'ROLLOUT' }[:partitions]
96
- .map { |partition| partition[:treatment] }
90
+ if @config.localhost_mode
91
+ treatments = split[:conditions]
92
+ .first[:partitions]
93
+ .map { |partition| partition[:treatment] }
94
+ else
95
+ treatments = split[:conditions]
96
+ .detect { |c| c[:conditionType] == 'ROLLOUT' }[:partitions]
97
+ .map { |partition| partition[:treatment] }
98
+ end
97
99
  rescue StandardError
98
100
  treatments = []
99
101
  end
100
-
101
102
  {
102
103
  name: name,
103
104
  traffic_type_name: split[:trafficTypeName],