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

Sign up to get free protection for your applications and to get access to all the features.
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],