splitclient-rb 6.3.0 → 8.11.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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/pull_request_template.md +9 -0
  4. data/.github/workflows/ci.yml +90 -0
  5. data/.github/workflows/update-license-year.yml +45 -0
  6. data/.gitignore +4 -0
  7. data/.rubocop.yml +46 -3
  8. data/CHANGES.txt +158 -11
  9. data/CONTRIBUTORS-GUIDE.md +49 -0
  10. data/LICENSE +169 -13
  11. data/NOTICE.txt +5 -0
  12. data/README.md +67 -27
  13. data/Rakefile +1 -8
  14. data/ext/murmurhash/3_x64_128.c +117 -0
  15. data/ext/murmurhash/murmurhash.c +5 -1
  16. data/lib/murmurhash/murmurhash.jar +0 -0
  17. data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +3 -3
  18. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +4 -0
  19. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +7 -0
  20. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +12 -4
  21. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +83 -0
  22. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +70 -0
  23. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +67 -0
  24. data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
  25. data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
  26. data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
  27. data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
  28. data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
  29. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +26 -14
  30. data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +9 -14
  31. data/lib/splitclient-rb/cache/repositories/events_repository.rb +31 -10
  32. data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
  33. data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
  34. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +22 -23
  35. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +15 -22
  36. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +6 -31
  37. data/lib/splitclient-rb/cache/repositories/repository.rb +6 -5
  38. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
  39. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +46 -6
  40. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +232 -43
  41. data/lib/splitclient-rb/cache/routers/impression_router.rb +24 -22
  42. data/lib/splitclient-rb/cache/senders/events_sender.rb +12 -29
  43. data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
  44. data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +69 -0
  45. data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +43 -0
  46. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +27 -13
  47. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +11 -25
  48. data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
  49. data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +47 -0
  50. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +95 -0
  51. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +110 -0
  52. data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
  53. data/lib/splitclient-rb/clients/split_client.rb +385 -138
  54. data/lib/splitclient-rb/constants.rb +16 -0
  55. data/lib/splitclient-rb/engine/api/client.rb +38 -43
  56. data/lib/splitclient-rb/engine/api/events.rb +19 -11
  57. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
  58. data/lib/splitclient-rb/engine/api/impressions.rb +49 -14
  59. data/lib/splitclient-rb/engine/api/segments.rb +31 -24
  60. data/lib/splitclient-rb/engine/api/splits.rb +108 -33
  61. data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
  62. data/lib/splitclient-rb/engine/auth_api_client.rb +96 -0
  63. data/lib/splitclient-rb/engine/back_off.rb +26 -0
  64. data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
  65. data/lib/splitclient-rb/engine/common/impressions_manager.rb +165 -0
  66. data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
  67. data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
  68. data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
  69. data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
  70. data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
  71. data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
  72. data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
  73. data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
  74. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +144 -0
  75. data/lib/splitclient-rb/engine/matchers/all_keys_matcher.rb +1 -1
  76. data/lib/splitclient-rb/engine/matchers/between_matcher.rb +7 -5
  77. data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
  78. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +10 -8
  79. data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +2 -6
  80. data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +1 -5
  81. data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +7 -5
  82. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +6 -5
  83. data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +5 -4
  84. data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +3 -2
  85. data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +6 -4
  86. data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
  87. data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +1 -5
  88. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +6 -4
  89. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
  90. data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
  91. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +6 -4
  92. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
  93. data/lib/splitclient-rb/engine/matchers/matcher.rb +22 -0
  94. data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +3 -2
  95. data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +3 -2
  96. data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +2 -6
  97. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  98. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
  99. data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
  100. data/lib/splitclient-rb/engine/matchers/set_matcher.rb +2 -1
  101. data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +4 -3
  102. data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +3 -2
  103. data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +7 -5
  104. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +3 -65
  105. data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
  106. data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
  107. data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
  108. data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
  109. data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
  110. data/lib/splitclient-rb/engine/models/label.rb +3 -0
  111. data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
  112. data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
  113. data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
  114. data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +14 -0
  115. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  116. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  117. data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
  118. data/lib/splitclient-rb/engine/parser/condition.rb +81 -20
  119. data/lib/splitclient-rb/engine/parser/evaluator.rb +40 -51
  120. data/lib/splitclient-rb/engine/push_manager.rb +66 -0
  121. data/lib/splitclient-rb/engine/status_manager.rb +39 -0
  122. data/lib/splitclient-rb/engine/sync_manager.rb +180 -0
  123. data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
  124. data/lib/splitclient-rb/exceptions.rb +20 -1
  125. data/lib/splitclient-rb/helpers/decryption_helper.rb +25 -0
  126. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  127. data/lib/splitclient-rb/helpers/repository_helper.rb +61 -0
  128. data/lib/splitclient-rb/helpers/thread_helper.rb +24 -0
  129. data/lib/splitclient-rb/helpers/util.rb +26 -0
  130. data/lib/splitclient-rb/managers/split_manager.rb +58 -20
  131. data/lib/splitclient-rb/spec.rb +9 -0
  132. data/lib/splitclient-rb/split_config.rb +336 -54
  133. data/lib/splitclient-rb/split_factory.rb +219 -33
  134. data/lib/splitclient-rb/split_factory_builder.rb +1 -22
  135. data/lib/splitclient-rb/split_factory_registry.rb +63 -0
  136. data/lib/splitclient-rb/split_logger.rb +9 -10
  137. data/lib/splitclient-rb/sse/event_source/client.rb +263 -0
  138. data/lib/splitclient-rb/sse/event_source/event_parser.rb +65 -0
  139. data/lib/splitclient-rb/sse/event_source/event_types.rb +15 -0
  140. data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
  141. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +84 -0
  142. data/lib/splitclient-rb/sse/notification_processor.rb +48 -0
  143. data/lib/splitclient-rb/sse/sse_handler.rb +44 -0
  144. data/lib/splitclient-rb/sse/workers/segments_worker.rb +62 -0
  145. data/lib/splitclient-rb/sse/workers/splits_worker.rb +149 -0
  146. data/lib/splitclient-rb/telemetry/domain/constants.rb +48 -0
  147. data/lib/splitclient-rb/telemetry/domain/structs.rb +35 -0
  148. data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
  149. data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
  150. data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
  151. data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
  152. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
  153. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
  154. data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
  155. data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
  156. data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +119 -0
  157. data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +87 -0
  158. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +213 -0
  159. data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
  160. data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
  161. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +27 -0
  162. data/lib/splitclient-rb/telemetry/runtime_consumer.rb +25 -0
  163. data/lib/splitclient-rb/telemetry/runtime_producer.rb +25 -0
  164. data/lib/splitclient-rb/telemetry/storages/memory.rb +159 -0
  165. data/lib/splitclient-rb/telemetry/sync_task.rb +36 -0
  166. data/lib/splitclient-rb/telemetry/synchronizer.rb +33 -0
  167. data/lib/splitclient-rb/utilitites.rb +8 -0
  168. data/lib/splitclient-rb/validators.rb +142 -38
  169. data/lib/splitclient-rb/version.rb +1 -1
  170. data/lib/splitclient-rb.rb +101 -16
  171. data/sonar-project.properties +6 -0
  172. data/splitclient-rb.gemspec +28 -23
  173. metadata +262 -82
  174. data/.travis.yml +0 -11
  175. data/Appraisals +0 -10
  176. data/Detailed-README.md +0 -588
  177. data/NEWS +0 -141
  178. data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +0 -127
  179. data/lib/splitclient-rb/cache/repositories/metrics/redis_repository.rb +0 -96
  180. data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +0 -21
  181. data/lib/splitclient-rb/cache/senders/metrics_sender.rb +0 -56
  182. data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +0 -46
  183. data/lib/splitclient-rb/cache/stores/segment_store.rb +0 -81
  184. data/lib/splitclient-rb/cache/stores/split_store.rb +0 -102
  185. data/lib/splitclient-rb/clients/localhost_split_client.rb +0 -183
  186. data/lib/splitclient-rb/engine/api/faraday_adapter/patched_net_http_persistent.rb +0 -46
  187. data/lib/splitclient-rb/engine/api/metrics.rb +0 -60
  188. data/lib/splitclient-rb/engine/metrics/metrics.rb +0 -80
  189. data/lib/splitclient-rb/engine/parser/split_adapter.rb +0 -81
  190. data/lib/splitclient-rb/localhost_split_factory.rb +0 -13
  191. data/lib/splitclient-rb/localhost_utils.rb +0 -59
  192. data/lib/splitclient-rb/managers/localhost_split_manager.rb +0 -60
@@ -4,54 +4,37 @@ module SplitIoClient
4
4
  module Cache
5
5
  module Senders
6
6
  class EventsSender
7
- def initialize(events_repository, api_key)
7
+ def initialize(events_repository, config)
8
8
  @events_repository = events_repository
9
- @api_key = api_key
9
+ @config = config
10
10
  end
11
11
 
12
- def call
13
- if ENV['SPLITCLIENT_ENV'] == 'test'
14
- post_events
15
- else
16
- events_thread
17
-
18
- if defined?(PhusionPassenger)
19
- PhusionPassenger.on_event(:starting_worker_process) do |forked|
20
- events_thread if forked
21
- end
22
- end
23
- end
12
+ def call
13
+ events_thread
24
14
  end
25
15
 
26
16
  private
27
17
 
28
18
  def events_thread
29
- SplitIoClient.configuration.threads[:events_sender] = Thread.new do
19
+ @config.threads[:events_sender] = Thread.new do
30
20
  begin
31
- SplitIoClient.configuration.logger.info('Starting events service')
32
-
21
+ @config.logger.info('Starting events service')
22
+
33
23
  loop do
34
- post_events(false)
24
+ post_events
35
25
 
36
- sleep(SplitIoClient::Utilities.randomize_interval(SplitIoClient.configuration.events_push_rate))
26
+ sleep(SplitIoClient::Utilities.randomize_interval(@config.events_push_rate))
37
27
  end
38
28
  rescue SplitIoClient::SDKShutdownException
39
29
  post_events
40
30
 
41
- SplitIoClient.configuration.logger.info('Posting events due to shutdown')
31
+ @config.logger.info('Posting events due to shutdown')
42
32
  end
43
33
  end
44
34
  end
45
35
 
46
- def post_events(fetch_all_events = true)
47
- events = fetch_all_events ? @events_repository.clear : @events_repository.batch
48
- events_api.post(events)
49
- rescue StandardError => error
50
- SplitIoClient.configuration.log_found_exception(__method__.to_s, error)
51
- end
52
-
53
- def events_api
54
- @events_api ||= SplitIoClient::Api::Events.new(@api_key)
36
+ def post_events
37
+ @events_repository.post_events
55
38
  end
56
39
  end
57
40
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class MemoryImpressionsSender < ImpressionsSenderAdapter
7
+ def initialize(config, telemetry_api, impressions_api)
8
+ @config = config
9
+ @telemetry_api = telemetry_api
10
+ @impressions_api = impressions_api
11
+ end
12
+
13
+ def record_uniques_key(uniques)
14
+ uniques_keys = uniques_formatter(uniques)
15
+
16
+ @telemetry_api.record_unique_keys(uniques_keys) unless uniques_keys.nil?
17
+ rescue StandardError => e
18
+ @config.log_found_exception(__method__.to_s, e)
19
+ end
20
+
21
+ def record_impressions_count(impressions_count)
22
+ counts = impressions_count_formatter(impressions_count)
23
+
24
+ @impressions_api.post_count(counts) unless counts.nil?
25
+ rescue StandardError => e
26
+ @config.log_found_exception(__method__.to_s, e)
27
+ end
28
+
29
+ private
30
+
31
+ def uniques_formatter(uniques)
32
+ return if uniques.nil? || uniques.empty?
33
+
34
+ to_return = { keys: [] }
35
+ uniques.each do |key, value|
36
+ to_return[:keys] << {
37
+ f: key,
38
+ ks: value.to_a
39
+ }
40
+ end
41
+
42
+ to_return
43
+ rescue StandardError => e
44
+ @config.log_found_exception(__method__.to_s, e)
45
+ nil
46
+ end
47
+
48
+ def impressions_count_formatter(counts)
49
+ return if counts.nil? || counts.empty?
50
+
51
+ formated_counts = {pf: []}
52
+
53
+ counts.each do |key, value|
54
+ key_splited = key.split('::')
55
+
56
+ formated_counts[:pf] << {
57
+ f: key_splited[0].to_s, # feature name
58
+ m: key_splited[1].to_i, # time frame
59
+ rc: value # count
60
+ }
61
+ end
62
+
63
+ formated_counts
64
+ rescue StandardError => e
65
+ @config.log_found_exception(__method__.to_s, e)
66
+ nil
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class RedisImpressionsSender < ImpressionsSenderAdapter
7
+ EXPIRE_SECONDS = 3600
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ @adapter = @config.impressions_adapter
12
+ end
13
+
14
+ def record_uniques_key(uniques)
15
+ return if uniques.nil? || uniques.empty?
16
+
17
+ size = @adapter.add_to_queue(unique_keys_key, uniques_formatter(uniques))
18
+
19
+ @adapter.expire(unique_keys_key, EXPIRE_SECONDS) if uniques.length == size
20
+ rescue StandardError => e
21
+ @config.log_found_exception(__method__.to_s, e)
22
+ end
23
+
24
+ def record_impressions_count(impressions_count)
25
+ return if impressions_count.nil? || impressions_count.empty?
26
+
27
+ result = @adapter.redis.pipelined do |pipeline|
28
+ impressions_count.each do |key, value|
29
+ pipeline.hincrby(impressions_count_key, key, value)
30
+ end
31
+ end
32
+
33
+ expire_impressions_count_key(impressions_count, result)
34
+ rescue StandardError => e
35
+ @config.log_found_exception(__method__.to_s, e)
36
+ end
37
+
38
+ private
39
+
40
+ def expire_impressions_count_key(impressions_count, pipeline_result)
41
+ total_count = impressions_count.sum { |_, value| value }
42
+ hlen = pipeline_result.last
43
+
44
+ @adapter.expire(impressions_count_key, EXPIRE_SECONDS) if impressions_count.size == hlen && (pipeline_result.sum - hlen) == total_count
45
+ end
46
+
47
+ def uniques_formatter(uniques)
48
+ to_return = []
49
+ uniques.each do |key, value|
50
+ to_return << {
51
+ f: key,
52
+ ks: value.to_a
53
+ }.to_json
54
+ end
55
+
56
+ to_return
57
+ end
58
+
59
+ def impressions_count_key
60
+ "#{@config.redis_namespace}.impressions.count"
61
+ end
62
+
63
+ def unique_keys_key
64
+ "#{@config.redis_namespace}.uniquekeys"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class ImpressionsCountSender
7
+ def initialize(config, impression_counter, impressions_sender_adapter)
8
+ @config = config
9
+ @impression_counter = impression_counter
10
+ @impressions_sender_adapter = impressions_sender_adapter
11
+ end
12
+
13
+ def call
14
+ impressions_count_thread
15
+ end
16
+
17
+ private
18
+
19
+ def impressions_count_thread
20
+ @config.threads[:impressions_count_sender] = Thread.new do
21
+ begin
22
+ @config.logger.info('Starting impressions count service')
23
+ loop do
24
+ sleep(@config.counter_refresh_rate)
25
+ post_impressions_count
26
+ end
27
+ rescue SplitIoClient::SDKShutdownException
28
+ post_impressions_count
29
+
30
+ @config.logger.info('Posting impressions count due to shutdown')
31
+ end
32
+ end
33
+ end
34
+
35
+ def post_impressions_count
36
+ @impressions_sender_adapter.record_impressions_count(@impression_counter.pop_all)
37
+ rescue StandardError => e
38
+ @config.log_found_exception(__method__.to_s, e)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -17,12 +17,10 @@ module SplitIoClient
17
17
 
18
18
  formatted_impressions = unique_features(filtered_impressions).each_with_object([]) do |feature, memo|
19
19
  feature_impressions = feature_impressions(filtered_impressions, feature)
20
- ip = feature_impressions.first[:m][:i]
21
20
  current_impressions = current_impressions(feature_impressions)
22
21
  memo << {
23
- testName: feature.to_sym,
24
- keyImpressions: current_impressions,
25
- ip: ip
22
+ f: feature.to_sym,
23
+ i: current_impressions
26
24
  }
27
25
  end
28
26
 
@@ -39,14 +37,28 @@ module SplitIoClient
39
37
 
40
38
  def current_impressions(feature_impressions)
41
39
  feature_impressions.map do |impression|
42
- {
43
- keyName: impression[:i][:k],
44
- treatment: impression[:i][:t],
45
- time: impression[:i][:m],
46
- bucketingKey: impression[:i][:b],
47
- label: impression[:i][:r],
48
- changeNumber: impression[:i][:c]
49
- }
40
+ if impression[:i][:properties].nil?
41
+ impression = {
42
+ k: impression[:i][:k],
43
+ t: impression[:i][:t],
44
+ m: impression[:i][:m],
45
+ b: impression[:i][:b],
46
+ r: impression[:i][:r],
47
+ c: impression[:i][:c],
48
+ pt: impression[:i][:pt]
49
+ }
50
+ else
51
+ impression = {
52
+ k: impression[:i][:k],
53
+ t: impression[:i][:t],
54
+ m: impression[:i][:m],
55
+ b: impression[:i][:b],
56
+ r: impression[:i][:r],
57
+ c: impression[:i][:c],
58
+ pt: impression[:i][:pt],
59
+ properties: impression[:i][:properties].to_json.to_s
60
+ }
61
+ end
50
62
  end
51
63
  end
52
64
 
@@ -73,7 +85,9 @@ module SplitIoClient
73
85
  "#{impression[:i][:k]}:" \
74
86
  "#{impression[:i][:b]}:" \
75
87
  "#{impression[:i][:c]}:" \
76
- "#{impression[:i][:t]}"
88
+ "#{impression[:i][:t]}:" \
89
+ "#{impression[:i][:pt]}" \
90
+ "#{impression[:i][:properties]}" \
77
91
  end
78
92
  end
79
93
  end
@@ -4,46 +4,32 @@ module SplitIoClient
4
4
  module Cache
5
5
  module Senders
6
6
  class ImpressionsSender
7
- def initialize(impressions_repository, api_key)
7
+ def initialize(impressions_repository, config, impressions_api)
8
8
  @impressions_repository = impressions_repository
9
- @api_key = api_key
9
+ @config = config
10
+ @impressions_api = impressions_api
10
11
  end
11
12
 
12
13
  def call
13
- if SplitIoClient.configuration.disable_impressions
14
- SplitIoClient.configuration.logger.info('Disabling impressions service by config')
15
- return
16
- end
17
-
18
- if ENV['SPLITCLIENT_ENV'] == 'test'
19
- post_impressions
20
- else
21
- impressions_thread
22
-
23
- if defined?(PhusionPassenger)
24
- PhusionPassenger.on_event(:starting_worker_process) do |forked|
25
- impressions_thread if forked
26
- end
27
- end
28
- end
14
+ impressions_thread
29
15
  end
30
16
 
31
17
  private
32
18
 
33
19
  def impressions_thread
34
- SplitIoClient.configuration.threads[:impressions_sender] = Thread.new do
20
+ @config.threads[:impressions_sender] = Thread.new do
35
21
  begin
36
- SplitIoClient.configuration.logger.info('Starting impressions service')
22
+ @config.logger.info('Starting impressions service')
37
23
 
38
24
  loop do
39
25
  post_impressions(false)
40
26
 
41
- sleep(SplitIoClient::Utilities.randomize_interval(SplitIoClient.configuration.impressions_refresh_rate))
27
+ sleep(SplitIoClient::Utilities.randomize_interval(@config.impressions_refresh_rate))
42
28
  end
43
29
  rescue SplitIoClient::SDKShutdownException
44
30
  post_impressions
45
31
 
46
- SplitIoClient.configuration.logger.info('Posting impressions due to shutdown')
32
+ @config.logger.info('Posting impressions due to shutdown')
47
33
  end
48
34
  end
49
35
  end
@@ -53,12 +39,12 @@ module SplitIoClient
53
39
  .call(fetch_all_impressions)
54
40
 
55
41
  impressions_api.post(formatted_impressions)
56
- rescue StandardError => error
57
- SplitIoClient.configuration.log_found_exception(__method__.to_s, error)
42
+ rescue StandardError => e
43
+ @config.log_found_exception(__method__.to_s, e)
58
44
  end
59
45
 
60
46
  def impressions_api
61
- @impressions_api ||= SplitIoClient::Api::Impressions.new(@api_key)
47
+ @impressions_api
62
48
  end
63
49
  end
64
50
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class ImpressionsSenderAdapter
7
+ extend Forwardable
8
+ def_delegators :@sender, :record_uniques_key, :record_impressions_count
9
+
10
+ def initialize(config, telemetry_api, impressions_api)
11
+ @sender = case config.telemetry_adapter.class.to_s
12
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
13
+ Cache::Senders::RedisImpressionsSender.new(config)
14
+ else
15
+ Cache::Senders::MemoryImpressionsSender.new(config, telemetry_api, impressions_api)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class LocalhostRepoCleaner
7
+ def initialize(impressions_repository, events_repository, config)
8
+ @impressions_repository = impressions_repository
9
+ @events_repository = events_repository
10
+ @config = config
11
+ end
12
+
13
+ def call
14
+ if ENV['SPLITCLIENT_ENV'] == 'test'
15
+ clear_repositories
16
+ else
17
+ cleaner_thread
18
+
19
+ if defined?(PhusionPassenger)
20
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
21
+ cleaner_thread if forked
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def cleaner_thread
30
+ @config.threads[:repo_cleaner] = Thread.new do
31
+ @config.logger.info('Starting repositories cleanup service')
32
+ loop do
33
+ clear_repositories
34
+
35
+ sleep(SplitIoClient::Utilities.randomize_interval(@config.features_refresh_rate))
36
+ end
37
+ end
38
+ end
39
+
40
+ def clear_repositories
41
+ @impressions_repository.clear
42
+ @events_repository.clear
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,95 @@
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
+ prerequisites: []
27
+ }
28
+ end
29
+
30
+ def build_configurations(treatments)
31
+ treatments.reduce({}) do |hash, treatment|
32
+ hash.merge(treatment[:treatment].to_sym => treatment[:config])
33
+ end
34
+ end
35
+
36
+ def build_conditions(treatments)
37
+ conditions = treatments.map do |treatment|
38
+ if treatment[:keys]
39
+ build_whitelist_treatment(treatment[:treatment], Array(treatment[:keys]))
40
+ else
41
+ build_rollout_treatment
42
+ end
43
+ .merge(partitions: build_partitions(treatment[:treatment], treatments))
44
+ end
45
+
46
+ conditions.sort_by { |condition| condition[:conditionType] }.reverse!
47
+ end
48
+
49
+ def build_whitelist_treatment(treatment_name, whitelist_keys)
50
+ {
51
+ conditionType: 'WHITELIST',
52
+ matcherGroup: {
53
+ combiner: 'AND',
54
+ matchers: [{
55
+ keySelector: nil,
56
+ matcherType: 'WHITELIST',
57
+ negate: false,
58
+ whitelistMatcherData: {
59
+ whitelist: whitelist_keys
60
+ }
61
+ }]
62
+ },
63
+ label: "whitelisted #{treatment_name}"
64
+ }
65
+ end
66
+
67
+ def build_rollout_treatment
68
+ {
69
+ conditionType: 'ROLLOUT',
70
+ matcherGroup: {
71
+ combiner: 'AND',
72
+ matchers: [
73
+ {
74
+ matcherType: 'ALL_KEYS',
75
+ negate: false
76
+ }
77
+ ]
78
+ },
79
+ label: 'default rule'
80
+ }
81
+ end
82
+
83
+ def build_partitions(current_treatment_name, treatments)
84
+ treatments.map do |treatment|
85
+ {
86
+ treatment: treatment[:treatment],
87
+ size: treatment[:treatment] == current_treatment_name ? 100 : 0
88
+ }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Stores
6
+ class LocalhostSplitStore
7
+ require 'yaml'
8
+ attr_reader :splits_repository
9
+
10
+ def initialize(splits_repository, config, status_manager = nil)
11
+ @splits_repository = splits_repository
12
+ @config = config
13
+ @status_manager = status_manager
14
+ end
15
+
16
+ def call
17
+ if ENV['SPLITCLIENT_ENV'] == 'test'
18
+ store_splits
19
+ else
20
+ splits_thread
21
+
22
+ if defined?(PhusionPassenger)
23
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
24
+ splits_thread if forked
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def splits_thread
33
+ @config.threads[:split_store] = Thread.new do
34
+ @config.logger.info('Starting feature flags fetcher service')
35
+ loop do
36
+ store_splits
37
+
38
+ sleep(StoreUtils.random_interval(@config.features_refresh_rate))
39
+ end
40
+ end
41
+ end
42
+
43
+ def store_splits
44
+ load_features.each do |split|
45
+ store_split(split)
46
+ end
47
+
48
+ @status_manager.ready! if @status_manager
49
+ rescue StandardError => e
50
+ @config.logger.error('Error while parsing the split file. ' \
51
+ 'Check that the input file matches the expected format')
52
+ @config.log_found_exception(__method__.to_s, e)
53
+ end
54
+
55
+ def store_split(split)
56
+ @config.logger.debug("storing feature flag (#{split[:name]})") if @config.debug_enabled
57
+
58
+ @splits_repository.update([split], [], -1)
59
+ end
60
+
61
+ def load_features
62
+ yaml_extensions = ['.yml', '.yaml']
63
+ if yaml_extensions.include? File.extname(@config.split_file)
64
+ parse_yaml_features
65
+ else
66
+ @config.logger.warn('Localhost mode: .split mocks ' \
67
+ 'will be deprecated soon in favor of YAML files, which provide more ' \
68
+ 'targeting power. Take a look in our documentation.')
69
+
70
+ parse_plain_text_features
71
+ end
72
+ end
73
+
74
+ def parse_plain_text_features
75
+ splits = File.open(@config.split_file).each_with_object({}) do |line, memo|
76
+ feature, treatment = line.strip.split(' ')
77
+
78
+ next if line.start_with?('#') || line.strip.empty?
79
+
80
+ memo[feature] = [{ treatment: treatment }]
81
+ end
82
+
83
+ LocalhostSplitBuilder.build_splits(splits)
84
+ end
85
+
86
+ def parse_yaml_features
87
+ splits = YAML.safe_load(File.read(@config.split_file)).each_with_object({}) do |feature, memo|
88
+ feat_symbolized_keys = symbolize_feat_keys(feature)
89
+
90
+ feat_name = feature.keys.first
91
+
92
+ if memo[feat_name]
93
+ memo[feat_name] << feat_symbolized_keys
94
+ else
95
+ memo[feat_name] = [feat_symbolized_keys]
96
+ end
97
+ end
98
+
99
+ LocalhostSplitBuilder.build_splits(splits)
100
+ end
101
+
102
+ def symbolize_feat_keys(yaml_feature)
103
+ yaml_feature.values.first.each_with_object({}) do |(k, v), memo|
104
+ memo[k.to_sym] = k == 'config' ? v.to_json : v
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ 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