splitclient-rb 1.0.2.wip → 8.11.1.pre.rc1-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 (210) hide show
  1. checksums.yaml +5 -5
  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 +20 -0
  7. data/.rubocop.yml +76 -0
  8. data/.simplecov +1 -0
  9. data/CHANGES.txt +361 -2
  10. data/CONTRIBUTORS-GUIDE.md +49 -0
  11. data/Gemfile +2 -0
  12. data/LICENSE +3 -36
  13. data/NOTICE.txt +5 -0
  14. data/README.md +59 -127
  15. data/Rakefile +27 -3
  16. data/ext/murmurhash/MurmurHash3.java +302 -0
  17. data/gemfiles/faraday_after_0.13.gemfile +7 -0
  18. data/gemfiles/faraday_before_0.13.gemfile +8 -0
  19. data/lib/murmurhash/base.rb +58 -0
  20. data/lib/murmurhash/murmurhash.jar +0 -0
  21. data/lib/murmurhash/murmurhash_mri.rb +3 -0
  22. data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +130 -0
  23. data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
  24. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +137 -0
  25. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +53 -0
  26. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +178 -0
  27. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +83 -0
  28. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +70 -0
  29. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +67 -0
  30. data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
  31. data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
  32. data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
  33. data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
  34. data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
  35. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +45 -0
  36. data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +35 -0
  37. data/lib/splitclient-rb/cache/repositories/events_repository.rb +61 -0
  38. data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
  39. data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
  40. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +56 -0
  41. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +57 -0
  42. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +23 -0
  43. data/lib/splitclient-rb/cache/repositories/repository.rb +24 -0
  44. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
  45. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +124 -0
  46. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +296 -0
  47. data/lib/splitclient-rb/cache/routers/impression_router.rb +60 -0
  48. data/lib/splitclient-rb/cache/senders/events_sender.rb +42 -0
  49. data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
  50. data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +69 -0
  51. data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +43 -0
  52. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +95 -0
  53. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +52 -0
  54. data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
  55. data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +47 -0
  56. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +95 -0
  57. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +110 -0
  58. data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
  59. data/lib/splitclient-rb/clients/split_client.rb +530 -0
  60. data/lib/splitclient-rb/constants.rb +16 -0
  61. data/lib/splitclient-rb/engine/api/client.rb +87 -0
  62. data/lib/splitclient-rb/engine/api/events.rb +57 -0
  63. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +58 -0
  64. data/lib/splitclient-rb/engine/api/impressions.rb +79 -0
  65. data/lib/splitclient-rb/engine/api/segments.rb +78 -0
  66. data/lib/splitclient-rb/engine/api/splits.rb +139 -0
  67. data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
  68. data/lib/splitclient-rb/engine/auth_api_client.rb +100 -0
  69. data/lib/splitclient-rb/engine/back_off.rb +26 -0
  70. data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
  71. data/lib/splitclient-rb/engine/common/impressions_manager.rb +165 -0
  72. data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
  73. data/lib/{splitclient-engine → splitclient-rb/engine}/evaluator/splitter.rb +35 -22
  74. data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
  75. data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
  76. data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
  77. data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
  78. data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
  79. data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
  80. data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
  81. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +144 -0
  82. data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/all_keys_matcher.rb +10 -21
  83. data/lib/splitclient-rb/engine/matchers/between_matcher.rb +47 -0
  84. data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
  85. data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/combiners.rb +4 -6
  86. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +80 -0
  87. data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +20 -0
  88. data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +16 -0
  89. data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +43 -0
  90. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +26 -0
  91. data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +42 -0
  92. data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +37 -0
  93. data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +44 -0
  94. data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
  95. data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +16 -0
  96. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +43 -0
  97. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
  98. data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
  99. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +43 -0
  100. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
  101. data/lib/splitclient-rb/engine/matchers/matcher.rb +52 -0
  102. data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +29 -0
  103. data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +47 -0
  104. data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +22 -0
  105. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  106. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
  107. data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
  108. data/lib/splitclient-rb/engine/matchers/set_matcher.rb +27 -0
  109. data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +40 -0
  110. data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +28 -0
  111. data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
  112. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +66 -0
  113. data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
  114. data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
  115. data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
  116. data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
  117. data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
  118. data/lib/splitclient-rb/engine/models/label.rb +10 -0
  119. data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
  120. data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
  121. data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
  122. data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +16 -0
  123. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  124. data/lib/splitclient-rb/engine/models/split.rb +17 -0
  125. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  126. data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
  127. data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
  128. data/lib/splitclient-rb/engine/parser/condition.rb +271 -0
  129. data/lib/splitclient-rb/engine/parser/evaluator.rb +112 -0
  130. data/lib/{splitclient-engine → splitclient-rb/engine}/parser/partition.rb +2 -4
  131. data/lib/splitclient-rb/engine/push_manager.rb +66 -0
  132. data/lib/splitclient-rb/engine/status_manager.rb +39 -0
  133. data/lib/splitclient-rb/engine/sync_manager.rb +183 -0
  134. data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
  135. data/lib/splitclient-rb/exceptions.rb +26 -0
  136. data/lib/splitclient-rb/helpers/decryption_helper.rb +25 -0
  137. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  138. data/lib/splitclient-rb/helpers/repository_helper.rb +61 -0
  139. data/lib/splitclient-rb/helpers/thread_helper.rb +24 -0
  140. data/lib/splitclient-rb/helpers/util.rb +26 -0
  141. data/lib/splitclient-rb/managers/split_manager.rb +121 -0
  142. data/lib/splitclient-rb/spec.rb +9 -0
  143. data/lib/splitclient-rb/split_config.rb +591 -47
  144. data/lib/splitclient-rb/split_factory.rb +284 -0
  145. data/lib/splitclient-rb/split_factory_builder.rb +7 -0
  146. data/lib/splitclient-rb/split_factory_registry.rb +63 -0
  147. data/lib/splitclient-rb/split_logger.rb +23 -0
  148. data/lib/splitclient-rb/sse/event_source/client.rb +264 -0
  149. data/lib/splitclient-rb/sse/event_source/event_parser.rb +65 -0
  150. data/lib/splitclient-rb/sse/event_source/event_types.rb +15 -0
  151. data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
  152. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +84 -0
  153. data/lib/splitclient-rb/sse/notification_processor.rb +48 -0
  154. data/lib/splitclient-rb/sse/sse_handler.rb +44 -0
  155. data/lib/splitclient-rb/sse/workers/segments_worker.rb +62 -0
  156. data/lib/splitclient-rb/sse/workers/splits_worker.rb +153 -0
  157. data/lib/splitclient-rb/telemetry/domain/constants.rb +48 -0
  158. data/lib/splitclient-rb/telemetry/domain/structs.rb +35 -0
  159. data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
  160. data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
  161. data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
  162. data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
  163. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
  164. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
  165. data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
  166. data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
  167. data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +119 -0
  168. data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +87 -0
  169. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +213 -0
  170. data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
  171. data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
  172. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +27 -0
  173. data/lib/splitclient-rb/telemetry/runtime_consumer.rb +25 -0
  174. data/lib/splitclient-rb/telemetry/runtime_producer.rb +25 -0
  175. data/lib/splitclient-rb/telemetry/storages/memory.rb +159 -0
  176. data/lib/splitclient-rb/telemetry/sync_task.rb +36 -0
  177. data/lib/splitclient-rb/telemetry/synchronizer.rb +33 -0
  178. data/lib/splitclient-rb/utilitites.rb +49 -0
  179. data/lib/splitclient-rb/validators.rb +361 -0
  180. data/lib/splitclient-rb/version.rb +1 -1
  181. data/lib/splitclient-rb.rb +177 -25
  182. data/sonar-project.properties +6 -0
  183. data/splitclient-rb.gemspec +53 -26
  184. data/tasks/benchmark_get_treatment.rake +31 -17
  185. data/tasks/irb.rake +6 -0
  186. metadata +411 -86
  187. data/NEWS +0 -9
  188. data/lib/splitclient-cache/local_store.rb +0 -45
  189. data/lib/splitclient-engine/impressions/impressions.rb +0 -79
  190. data/lib/splitclient-engine/matchers/between_matcher.rb +0 -48
  191. data/lib/splitclient-engine/matchers/combining_matcher.rb +0 -94
  192. data/lib/splitclient-engine/matchers/equal_to_matcher.rb +0 -48
  193. data/lib/splitclient-engine/matchers/greater_than_or_equal_to_matcher.rb +0 -48
  194. data/lib/splitclient-engine/matchers/less_than_or_equal_to_matcher.rb +0 -48
  195. data/lib/splitclient-engine/matchers/negation_matcher.rb +0 -54
  196. data/lib/splitclient-engine/matchers/user_defined_segment_matcher.rb +0 -61
  197. data/lib/splitclient-engine/matchers/whitelist_matcher.rb +0 -64
  198. data/lib/splitclient-engine/metrics/binary_search_latency_tracker.rb +0 -122
  199. data/lib/splitclient-engine/metrics/metrics.rb +0 -158
  200. data/lib/splitclient-engine/parser/condition.rb +0 -143
  201. data/lib/splitclient-engine/parser/segment.rb +0 -84
  202. data/lib/splitclient-engine/parser/segment_parser.rb +0 -46
  203. data/lib/splitclient-engine/parser/split.rb +0 -68
  204. data/lib/splitclient-engine/parser/split_adapter.rb +0 -433
  205. data/lib/splitclient-engine/parser/split_parser.rb +0 -139
  206. data/lib/splitclient-engine/partitions/treatments.rb +0 -40
  207. data/lib/splitclient-rb/split_client.rb +0 -156
  208. data/lib/splitclient-rb_utilitites.rb +0 -26
  209. data/tasks/console.rake +0 -4
  210. data/tasks/rspec.rake +0 -3
@@ -0,0 +1,530 @@
1
+ module SplitIoClient
2
+ EVENTS_SIZE_THRESHOLD = 32768
3
+ EVENT_AVERAGE_SIZE = 1024
4
+ GET_TREATMENT = 'get_treatment'
5
+ GET_TREATMENTS = 'get_treatments'
6
+ GET_TREATMENT_WITH_CONFIG = 'get_treatment_with_config'
7
+ GET_TREATMENTS_WITH_CONFIG = 'get_treatments_with_config'
8
+ GET_TREATMENTS_BY_FLAG_SET = 'get_treatments_by_flag_set'
9
+ GET_TREATMENTS_BY_FLAG_SETS = 'get_treatments_by_flag_sets'
10
+ GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = 'get_treatments_with_config_by_flag_set'
11
+ GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = 'get_treatments_with_config_by_flag_sets'
12
+ TRACK = 'track'
13
+
14
+ class SplitClient
15
+ #
16
+ # Creates a new split client instance that connects to split.io API.
17
+ #
18
+ # @param sdk_key [String] the SDK key for your split account
19
+ #
20
+ # @return [SplitIoClient] split.io client instance
21
+ def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator, fallback_treatment_calculator, events_manager)
22
+ @api_key = sdk_key
23
+ @splits_repository = repositories[:splits]
24
+ @segments_repository = repositories[:segments]
25
+ @rule_based_segments_repository = repositories[:rule_based_segments]
26
+ @impressions_repository = repositories[:impressions]
27
+ @events_repository = repositories[:events]
28
+ @status_manager = status_manager
29
+ @destroyed = false
30
+ @config = config
31
+ @impressions_manager = impressions_manager
32
+ @telemetry_evaluation_producer = telemetry_evaluation_producer
33
+ @split_validator = split_validator
34
+ @evaluator = evaluator
35
+ @fallback_treatment_calculator = fallback_treatment_calculator
36
+ @events_manager = events_manager
37
+ end
38
+
39
+ def get_treatment(
40
+ key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
41
+ multiple = false, evaluator = nil
42
+ )
43
+ log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
44
+
45
+ result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple, evaluation_options)
46
+ return result.tap { |t| t.delete(:config) } if multiple
47
+ result[:treatment]
48
+ end
49
+
50
+ def get_treatment_with_config(
51
+ key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
52
+ multiple = false, evaluator = nil
53
+ )
54
+ log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
55
+ result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
56
+
57
+ { :config => result[:config], :treatment => result[:treatment] }
58
+ end
59
+
60
+ def get_treatments(key, split_names, attributes = {}, evaluation_options = nil)
61
+ treatments = treatments(key, split_names, attributes, evaluation_options)
62
+
63
+ return treatments if treatments.nil?
64
+ keys = treatments.keys
65
+ treats = treatments.map { |_,t| t[:treatment] }
66
+ Hash[keys.zip(treats)]
67
+ end
68
+
69
+ def get_treatments_with_config(key, split_names, attributes = {}, evaluation_options = nil)
70
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)
71
+
72
+ results.map{|key, value|
73
+ [key, { treatment: value[:treatment], config: value[:config] }]
74
+ }.to_h
75
+ end
76
+
77
+ def get_treatments_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
78
+ valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SET, [flag_set])
79
+ split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
80
+ treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SET)
81
+ return treatments if treatments.nil?
82
+ keys = treatments.keys
83
+ treats = treatments.map { |_,t| t[:treatment] }
84
+ Hash[keys.zip(treats)]
85
+ end
86
+
87
+ def get_treatments_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
88
+ valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SETS, flag_sets)
89
+ split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
90
+ treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SETS)
91
+ return treatments if treatments.nil?
92
+ keys = treatments.keys
93
+ treats = treatments.map { |_,t| t[:treatment] }
94
+ Hash[keys.zip(treats)]
95
+ end
96
+
97
+ def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
98
+ valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set])
99
+ split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
100
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
101
+
102
+ results.map{|key, value|
103
+ [key, { treatment: value[:treatment], config: value[:config] }]
104
+ }.to_h
105
+ end
106
+
107
+ def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
108
+ valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets)
109
+ split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
110
+ results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
111
+
112
+ results.map{|key, value|
113
+ [key, { treatment: value[:treatment], config: value[:config] }]
114
+ }.to_h
115
+ end
116
+
117
+ def destroy
118
+ @config.logger.info('Split client shutdown started...') if @config.debug_enabled
119
+ if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && @config.impressions_mode != :none &&
120
+ (!@impressions_repository.empty? || !@events_repository.empty?)
121
+ @config.logger.debug("Impressions and/or Events cache is not empty") if @config.debug_enabled
122
+ # Adding small delay to ensure sender threads are fully running
123
+ sleep(0.1)
124
+ if !@config.threads.key?(:impressions_sender) || !@config.threads.key?(:events_sender)
125
+ @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") if @config.debug_enabled
126
+ @config.threads[:start_sdk].join(5) if @config.threads.key?(:start_sdk)
127
+ end
128
+ end
129
+ @config.threads.select { |name, thread| name.to_s.end_with? 'sender' }.values.each do |thread|
130
+ thread.raise(SplitIoClient::SDKShutdownException)
131
+ thread.join
132
+ end
133
+
134
+ @config.threads.values.each { |thread| Thread.kill(thread) }
135
+
136
+ @splits_repository.clear
137
+ @segments_repository.clear
138
+ @rule_based_segments_repository.clear
139
+
140
+ SplitIoClient.load_factory_registry
141
+ SplitIoClient.split_factory_registry.remove(@api_key)
142
+
143
+ @config.logger.info('Split client shutdown complete') if @config.debug_enabled
144
+ @config.valid_mode = false
145
+ @destroyed = true
146
+ end
147
+
148
+ def track(key, traffic_type_name, event_type, value = nil, properties = nil)
149
+ return false unless valid_client && @config.split_validator.valid_track_parameters(key, traffic_type_name, event_type, value, properties)
150
+
151
+ start = Time.now
152
+ properties_size = EVENT_AVERAGE_SIZE
153
+
154
+ if !properties.nil?
155
+ properties, size = validate_properties(properties)
156
+ properties_size += size
157
+ return false unless check_properties_size(properties_size)
158
+ end
159
+
160
+ if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name)
161
+ @config.logger.warn("track: Traffic Type #{traffic_type_name} " \
162
+ "does not have any corresponding feature flags in this environment, make sure you're tracking " \
163
+ 'your events to a valid traffic type defined in the Split user interface')
164
+ end
165
+
166
+ @events_repository.add(key.to_s, traffic_type_name.downcase, event_type.to_s, (Time.now.to_f * 1000).to_i, value, properties, properties_size)
167
+ record_latency(TRACK, start)
168
+ true
169
+ rescue StandardError => e
170
+ @config.log_found_exception(__method__.to_s, e)
171
+ record_exception(TRACK)
172
+
173
+ false
174
+ end
175
+
176
+ def block_until_ready(time = nil)
177
+ @status_manager.wait_until_ready(time) if @status_manager
178
+ end
179
+
180
+ def register(sdk_event, handler)
181
+ @events_manager.register(sdk_event, handler)
182
+ end
183
+
184
+ def unregister(sdk_event, handler)
185
+ @events_manager.unregister(sdk_event)
186
+ end
187
+
188
+ private
189
+
190
+ def check_properties_size(properties_size, msg = "Event not queued")
191
+ if (properties_size > EVENTS_SIZE_THRESHOLD)
192
+ @config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. #{msg}")
193
+ return false
194
+ end
195
+ return true
196
+ end
197
+
198
+ def keys_from_key(key)
199
+ case key
200
+ when Hash
201
+ key.values_at(:bucketing_key, :matching_key).map { |k| k.nil? ? nil : k }
202
+ else
203
+ [nil, key].map { |k| k.nil? ? nil : k }
204
+ end
205
+ end
206
+
207
+ def parsed_treatment(treatment_data, multiple = false)
208
+ if multiple
209
+ {
210
+ treatment: treatment_data[:treatment],
211
+ label: treatment_data[:label],
212
+ change_number: treatment_data[:change_number],
213
+ config: treatment_data[:config]
214
+ }
215
+ else
216
+ {
217
+ treatment: treatment_data[:treatment],
218
+ config: treatment_data[:config],
219
+ }
220
+ end
221
+ end
222
+
223
+ def sanitize_split_names(calling_method, split_names)
224
+ return nil if !split_names.is_a?(Array)
225
+
226
+ split_names.compact.uniq.select do |split_name|
227
+ if split_name.nil?
228
+ false
229
+ elsif (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty?
230
+ true
231
+ elsif split_name.is_a?(String) && split_name.empty?
232
+ @config.logger.warn("#{calling_method}: you passed an empty feature_flag_name, flag name must be a non-empty String or a Symbol")
233
+ false
234
+ else
235
+ @config.logger.warn("#{calling_method}: you passed an invalid feature_flag_name, flag name must be a non-empty String or a Symbol")
236
+ false
237
+ end
238
+ end
239
+ end
240
+
241
+ def validate_properties(properties, method = 'Event')
242
+ properties_count = 0
243
+ size = 0
244
+
245
+ fixed_properties = properties.each_with_object({}) { |(key, value), result|
246
+
247
+ if(key.is_a?(String) || key.is_a?(Symbol))
248
+ properties_count += 1
249
+ size += variable_size(key)
250
+ if value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass) || value.nil?
251
+ result[key] = value
252
+ size += variable_size(value)
253
+ else
254
+ @config.logger.warn("Property #{key} is of invalid type. Setting value to nil")
255
+ result[key] = nil
256
+ end
257
+ end
258
+ }
259
+
260
+ @config.logger.warn("#{method} has more than 300 properties. Some of them will be trimmed when processed") if properties_count > 300
261
+
262
+ return fixed_properties, size
263
+ end
264
+
265
+ def validate_evaluation_options(evaluation_options)
266
+ if !evaluation_options.is_a?(SplitIoClient::Engine::Models::EvaluationOptions)
267
+ @config.logger.warn("Option #{evaluation_options} should be a EvaluationOptions type. Setting value to nil")
268
+ return nil, 0
269
+ end
270
+ evaluation_options.properties = evaluation_options.properties.transform_keys(&:to_sym)
271
+ evaluation_options.properties, size = validate_properties(evaluation_options.properties, 'Treatment')
272
+ return evaluation_options, size
273
+ end
274
+
275
+ def valid_client
276
+ if @destroyed
277
+ @config.logger.error('Client has already been destroyed - no calls possible')
278
+ return false
279
+ end
280
+ @config.valid_mode
281
+ end
282
+
283
+ def treatments(key, feature_flag_names, attributes = {}, evaluation_options = nil, calling_method = 'get_treatments')
284
+ sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names)
285
+
286
+ if sanitized_feature_flag_names.nil?
287
+ @config.logger.error("#{calling_method}: feature_flag_names must be a non-empty Array")
288
+ return nil
289
+ end
290
+
291
+ if sanitized_feature_flag_names.empty?
292
+ @config.logger.error("#{calling_method}: feature_flag_names must be a non-empty Array")
293
+ return {}
294
+ end
295
+
296
+ bucketing_key, matching_key = keys_from_key(key)
297
+ bucketing_key = bucketing_key ? bucketing_key.to_s : nil
298
+ matching_key = matching_key ? matching_key.to_s : nil
299
+ attributes = parsed_attributes(attributes)
300
+
301
+ if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes)
302
+ to_return = Hash.new
303
+ sanitized_feature_flag_names.each {|name|
304
+ to_return[name.to_sym] = check_fallback_treatment(name, '')
305
+ }
306
+ return to_return
307
+ end
308
+
309
+ if !ready?
310
+ impressions = []
311
+ to_return = Hash.new
312
+ sanitized_feature_flag_names.each {|name|
313
+ treatment_data = check_fallback_treatment(name, Engine::Models::Label::NOT_READY)
314
+ to_return[name.to_sym] = treatment_data
315
+
316
+ impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym,
317
+ get_treatment_without_config(treatment_data), false, { attributes: attributes, time: nil },
318
+ evaluation_options), :disabled => false }
319
+ }
320
+ @impressions_manager.track(impressions)
321
+ return to_return
322
+ end
323
+
324
+ valid_feature_flag_names = []
325
+ sanitized_feature_flag_names.each { |feature_flag_name|
326
+ valid_feature_flag_names << feature_flag_name unless feature_flag_name.nil?
327
+ }
328
+ start = Time.now
329
+
330
+ feature_flags = @splits_repository.splits(valid_feature_flag_names)
331
+ treatments = Hash.new
332
+ invalid_treatments = Hash.new
333
+ feature_flags.each do |key, feature_flag|
334
+ if feature_flag.nil?
335
+ @config.logger.warn("#{calling_method}: you passed #{key} that " \
336
+ 'does not exist in this environment, please double check what feature flags exist in the Split user interface')
337
+ invalid_treatments[key] = check_fallback_treatment(key, Engine::Models::Label::NOT_FOUND)
338
+ next
339
+ end
340
+ treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method, false, evaluation_options)
341
+ treatments[key] =
342
+ {
343
+ treatment: treatments_labels_change_numbers[:treatment],
344
+ config: treatments_labels_change_numbers[:config]
345
+ }
346
+ @impressions_manager.track(impressions) unless impressions.empty?
347
+ end
348
+ record_latency(calling_method, start)
349
+
350
+ treatments.merge(invalid_treatments)
351
+ end
352
+
353
+ #
354
+ # obtains the treatment for a given feature
355
+ #
356
+ # @param key [String/Hash] user id or hash with matching_key/bucketing_key
357
+ # @param split_name [String/Array] name of the feature that is being validated or array of them
358
+ # @param attributes [Hash] attributes to pass to the treatment class
359
+ # @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
360
+ # @param store_impressions [Boolean] impressions aren't stored if this flag is false
361
+ # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
362
+ def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = nil,
363
+ calling_method = 'get_treatment', multiple = false, evaluation_options = nil)
364
+
365
+ log_deprecated_warning(calling_method, split_data, 'split_data')
366
+ log_deprecated_warning(calling_method, store_impressions, 'store_impressions')
367
+
368
+ impressions = []
369
+ bucketing_key, matching_key = keys_from_key(key)
370
+
371
+ attributes = parsed_attributes(attributes)
372
+
373
+ return parsed_treatment(check_fallback_treatment(feature_flag_name, ""), multiple) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes)
374
+
375
+ bucketing_key = bucketing_key ? bucketing_key.to_s : nil
376
+ matching_key = matching_key.to_s
377
+ sanitized_feature_flag_name = feature_flag_name.to_s.strip
378
+
379
+ if feature_flag_name.to_s != sanitized_feature_flag_name
380
+ @config.logger.warn("#{calling_method}: feature_flag_name #{feature_flag_name} has extra whitespace, trimming")
381
+ feature_flag_name = sanitized_feature_flag_name
382
+ end
383
+
384
+ feature_flag = @splits_repository.get_split(feature_flag_name)
385
+ treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple, evaluation_options)
386
+
387
+ @impressions_manager.track(impressions_decorator) unless impressions_decorator.nil?
388
+ treatments
389
+ end
390
+
391
+ def log_deprecated_warning(calling_method, parameter, parameter_name)
392
+ @config.logger.warn("#{calling_method}: detected #{parameter_name} parameter used, this parameter is deprecated and its value is ignored.") unless parameter.nil?
393
+ end
394
+
395
+ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false, evaluation_options = nil)
396
+ impressions_decorator = []
397
+ begin
398
+ start = Time.now
399
+ if feature_flag.nil? && ready?
400
+ @config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \
401
+ 'does not exist in this environment, please double check what feature flags exist in the Split user interface')
402
+ return check_fallback_treatment(feature_flag_name, Engine::Models::Label::NOT_FOUND), nil
403
+ end
404
+
405
+ if !feature_flag.nil? && ready?
406
+ treatment_data = @evaluator.evaluate_feature_flag(
407
+ { bucketing_key: bucketing_key, matching_key: matching_key }, feature_flag, attributes
408
+ )
409
+ impressions_disabled = feature_flag[:impressionsDisabled]
410
+ else
411
+ @config.logger.error("#{calling_method}: the SDK is not ready, results may be incorrect for feature flag #{feature_flag_name}. Make sure to wait for SDK readiness before using this method.")
412
+ treatment_data = check_fallback_treatment(feature_flag_name, Engine::Models::Label::NOT_READY)
413
+ impressions_disabled = false
414
+ end
415
+
416
+ evaluation_options, size = validate_evaluation_options(evaluation_options) unless evaluation_options.nil?
417
+ evaluation_options.properties = nil unless evaluation_options.nil? or check_properties_size((EVENT_AVERAGE_SIZE + size), "Properties are ignored")
418
+
419
+ record_latency(calling_method, start)
420
+ impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }, evaluation_options), :disabled => impressions_disabled }
421
+ impressions_decorator << impression_decorator unless impression_decorator.nil?
422
+ rescue StandardError => e
423
+ @config.log_found_exception(__method__.to_s, e)
424
+ record_exception(calling_method)
425
+ treatment_data = check_fallback_treatment(feature_flag_name, Engine::Models::Label::EXCEPTION)
426
+ impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, get_treatment_without_config(treatment_data), false, { attributes: attributes, time: nil }, evaluation_options), :disabled => false }
427
+
428
+ impressions_decorator << impression_decorator unless impression_decorator.nil?
429
+
430
+ return parsed_treatment(treatment_data, multiple), impressions_decorator
431
+ end
432
+ return parsed_treatment(treatment_data, multiple), impressions_decorator
433
+ end
434
+
435
+ def variable_size(value)
436
+ value.is_a?(String) ? value.length : 0
437
+ end
438
+
439
+ def ready?
440
+ return @status_manager.ready? if @status_manager
441
+ true
442
+ end
443
+
444
+ def parsed_attributes(attributes)
445
+ return attributes || attributes.to_h
446
+ end
447
+
448
+ def record_latency(method, start)
449
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
450
+
451
+ case method
452
+ when GET_TREATMENT
453
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT, bucket)
454
+ when GET_TREATMENTS
455
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS, bucket)
456
+ when GET_TREATMENT_WITH_CONFIG
457
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG, bucket)
458
+ when GET_TREATMENTS_WITH_CONFIG
459
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG, bucket)
460
+ when GET_TREATMENTS_BY_FLAG_SET
461
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET, bucket)
462
+ when GET_TREATMENTS_BY_FLAG_SETS
463
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS, bucket)
464
+ when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET
465
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET, bucket)
466
+ when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS
467
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, bucket)
468
+ when TRACK
469
+ @telemetry_evaluation_producer.record_latency(Telemetry::Domain::Constants::TRACK, bucket)
470
+ end
471
+ end
472
+
473
+ def record_exception(method)
474
+ case method
475
+ when GET_TREATMENT
476
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENT)
477
+ when GET_TREATMENTS
478
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS)
479
+ when GET_TREATMENT_WITH_CONFIG
480
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENT_WITH_CONFIG)
481
+ when GET_TREATMENTS_WITH_CONFIG
482
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG)
483
+ when GET_TREATMENTS_BY_FLAG_SET
484
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SET)
485
+ when GET_TREATMENTS_BY_FLAG_SETS
486
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_BY_FLAG_SETS)
487
+ when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET
488
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
489
+ when GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS
490
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
491
+ when TRACK
492
+ @telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TRACK)
493
+ end
494
+ end
495
+
496
+ def check_fallback_treatment(feature_name, label)
497
+ return {
498
+ label: (label != '')? label : nil,
499
+ treatment: Engine::Models::Treatment::CONTROL,
500
+ config: nil,
501
+ change_number: nil
502
+ } unless feature_name.is_a?(Symbol) || feature_name.is_a?(String)
503
+
504
+ fallback_treatment = @fallback_treatment_calculator.resolve(feature_name.to_sym, label)
505
+
506
+ {
507
+ label: (label != '')? fallback_treatment.label : nil,
508
+ treatment: fallback_treatment.treatment,
509
+ config: get_fallback_config(fallback_treatment),
510
+ change_number: nil
511
+ }
512
+ end
513
+
514
+ def get_treatment_without_config(treatment)
515
+ {
516
+ label: treatment[:label],
517
+ treatment: treatment[:treatment],
518
+ }
519
+ end
520
+
521
+ def get_fallback_config(fallback_treatment)
522
+ if fallback_treatment.config != nil
523
+ return fallback_treatment.config
524
+ end
525
+
526
+ return nil
527
+ end
528
+
529
+ end
530
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SplitIoClient::Constants
4
+ EXPIRATION_RATE = 600
5
+ CONTROL_PRI = 'control_pri'
6
+ CONTROL_SEC = 'control_sec'
7
+ OCCUPANCY_CHANNEL_PREFIX = '[?occupancy=metrics.publishers]'
8
+ FETCH_BACK_OFF_BASE_RETRIES = 1
9
+ PUSH_CONNECTED = 'PUSH_CONNECTED'
10
+ PUSH_RETRYABLE_ERROR = 'PUSH_RETRYABLE_ERROR'
11
+ PUSH_NONRETRYABLE_ERROR = 'PUSH_NONRETRYABLE_ERROR'
12
+ PUSH_SUBSYSTEM_DOWN = 'PUSH_SUBSYSTEM_DOWN'
13
+ PUSH_SUBSYSTEM_READY = 'PUSH_SUBSYSTEM_READY'
14
+ PUSH_SUBSYSTEM_OFF = 'PUSH_SUBSYSTEM_OFF'
15
+ PUSH_FORCED_STOP = 'PUSH_FORCED_STOP'
16
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Api
5
+ class Client
6
+ def initialize(config)
7
+ @config = config
8
+
9
+ check_faraday_compatibility
10
+ end
11
+
12
+ def get_api(url, api_key, params = {}, cache_control_headers = false)
13
+ api_client.options.params_encoder.sort_params = false
14
+ api_client.get(url, params) do |req|
15
+ req.headers = common_headers(api_key).merge('Accept-Encoding' => 'gzip')
16
+ req.headers = req.headers.merge('Cache-Control' => 'no-cache') if cache_control_headers
17
+
18
+ req.options[:timeout] = @config.read_timeout
19
+ req.options[:open_timeout] = @config.connection_timeout
20
+
21
+ @config.split_logger.log_if_debug("GET #{url} proxy: #{api_client.proxy}")
22
+ end
23
+
24
+ rescue Zlib::GzipFile::Error => e
25
+ @config.logger.warn("Incorrect formatted response exception: #{e}\n")
26
+ SplitIoClient::Engine::Models::SplitHttpResponse.new(400, '', true)
27
+
28
+ rescue StandardError => e
29
+ @config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
30
+ raise e, 'Split SDK failed to connect to backend to retrieve information', e.backtrace
31
+ end
32
+
33
+ def post_api(url, api_key, data, headers = {}, params = {})
34
+ api_client.post(url) do |req|
35
+ req.headers = common_headers(api_key)
36
+ .merge('Content-Type' => 'application/json')
37
+ .merge(headers)
38
+
39
+ machine_ip = @config.machine_ip
40
+ machine_name = @config.machine_name
41
+
42
+ req.headers = req.headers.merge('SplitSDKMachineIP' => machine_ip) unless machine_ip.empty? || machine_ip == 'unknown'
43
+ req.headers = req.headers.merge('SplitSDKMachineName' => machine_name) unless machine_name.empty? || machine_name == 'unknown'
44
+
45
+ req.body = data.to_json
46
+
47
+ req.options[:timeout] = @config.read_timeout
48
+ req.options[:open_timeout] = @config.connection_timeout
49
+
50
+ @config.split_logger.log_if_transport("POST #{url} #{req.body}")
51
+ @config.split_logger.log_if_debug("POST #{url}")
52
+ end
53
+ rescue StandardError => e
54
+ @config.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
55
+ raise e, 'Split SDK failed to connect to backend to post information', e.backtrace
56
+ end
57
+
58
+ def sdk_url_overriden?
59
+ @config.sdk_url_overriden?
60
+ end
61
+ private
62
+
63
+ def api_client
64
+ @api_client ||= Faraday.new do |builder|
65
+ builder.use SplitIoClient::FaradayMiddleware::Gzip
66
+ builder.adapter :net_http_persistent
67
+ builder.options.params_encoder = Faraday::FlatParamsEncoder
68
+ end
69
+ end
70
+
71
+ def common_headers(api_key)
72
+ {
73
+ 'Authorization' => "Bearer #{api_key}",
74
+ 'SplitSDKVersion' => "#{@config.language}-#{@config.version}",
75
+ }
76
+ end
77
+
78
+ def check_faraday_compatibility
79
+ version = Faraday::VERSION.split('.')[0]
80
+
81
+ require 'faraday/net_http_persistent' if version.to_i >= 2
82
+ rescue StandardError => e
83
+ @config.logger.warn(e)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Api
5
+ class Events < Client
6
+ def initialize(api_key, config, telemetry_runtime_producer)
7
+ super(config)
8
+ @api_key = api_key
9
+ @telemetry_runtime_producer = telemetry_runtime_producer
10
+ end
11
+
12
+ def post(events)
13
+ if events.empty?
14
+ @config.split_logger.log_if_debug('No events to report')
15
+ return
16
+ end
17
+
18
+ start = Time.now
19
+
20
+ events.each_slice(@config.events_queue_size) do |events_slice|
21
+ response = post_api(
22
+ "#{@config.events_uri}/events/bulk",
23
+ @api_key,
24
+ events_slice.map { |event| formatted_event(event[:e]) }
25
+ )
26
+
27
+ if response.success?
28
+ @config.split_logger.log_if_debug("Events reported: #{events_slice.size}")
29
+
30
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
31
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::EVENT_SYNC, bucket)
32
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::EVENT_SYNC, (Time.now.to_f * 1000.0).to_i)
33
+ else
34
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::EVENT_SYNC, response.status)
35
+
36
+ @config.logger.error("Unexpected status code while posting events: #{response.status}." \
37
+ ' - Check your API key and base URI')
38
+ raise 'Split SDK failed to connect to backend to post events'
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def formatted_event(event)
46
+ {
47
+ key: event[:key],
48
+ trafficTypeName: event[:trafficTypeName],
49
+ eventTypeId: event[:eventTypeId],
50
+ value: event[:value].to_f,
51
+ timestamp: event[:timestamp].to_i,
52
+ properties: event[:properties]
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end