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
@@ -6,42 +6,17 @@ module SplitIoClient
6
6
  # Repository which forwards impressions interface to the selected adapter
7
7
  class ImpressionsRepository < Repository
8
8
  extend Forwardable
9
- def_delegators :@adapter, :add, :add_bulk, :batch, :clear, :empty?
9
+ def_delegators :@repository, :add_bulk, :batch, :clear, :empty?
10
10
 
11
- def initialize(adapter)
12
- @adapter = case adapter.class.to_s
11
+ def initialize(config)
12
+ super(config)
13
+ @repository = case @config.impressions_adapter.class.to_s
13
14
  when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
14
- Repositories::Impressions::MemoryRepository.new(adapter)
15
+ Repositories::Impressions::MemoryRepository.new(@config)
15
16
  when 'SplitIoClient::Cache::Adapters::RedisAdapter'
16
- Repositories::Impressions::RedisRepository.new(adapter)
17
+ Repositories::Impressions::RedisRepository.new(@config)
17
18
  end
18
19
  end
19
-
20
- protected
21
-
22
- def impression_data(matching_key, bucketing_key, split_name, treatment, timestamp)
23
- {
24
- k: matching_key,
25
- b: bucketing_key,
26
- f: split_name,
27
- t: treatment[:treatment],
28
- r: applied_rule(treatment[:label]),
29
- c: treatment[:change_number],
30
- m: timestamp
31
- }
32
- end
33
-
34
- def metadata
35
- {
36
- s: "#{SplitIoClient.configuration.language}-#{SplitIoClient.configuration.version}",
37
- i: SplitIoClient.configuration.machine_ip,
38
- n: SplitIoClient.configuration.machine_name
39
- }
40
- end
41
-
42
- def applied_rule(label)
43
- SplitIoClient.configuration.labels_enabled ? label : nil
44
- end
45
20
  end
46
21
  end
47
22
  end
@@ -1,6 +1,11 @@
1
1
  module SplitIoClient
2
2
  module Cache
3
3
  class Repository
4
+
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
4
9
  def set_string(key, str)
5
10
  @adapter.set_string(namespace_key(key), str)
6
11
  end
@@ -12,11 +17,7 @@ module SplitIoClient
12
17
  protected
13
18
 
14
19
  def namespace_key(key = '')
15
- "#{SplitIoClient.configuration.redis_namespace}#{key}"
16
- end
17
-
18
- def impressions_metrics_key(key)
19
- namespace_key("/#{SplitIoClient.configuration.language}-#{SplitIoClient.configuration.version}/#{SplitIoClient.configuration.machine_ip}/#{key}")
20
+ "#{@config.redis_namespace}#{key}"
20
21
  end
21
22
  end
22
23
  end
@@ -0,0 +1,136 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Repositories
6
+ class RuleBasedSegmentsRepository < Repository
7
+ attr_reader :adapter
8
+ DEFAULT_CONDITIONS_TEMPLATE = [{
9
+ conditionType: "ROLLOUT",
10
+ matcherGroup: {
11
+ combiner: "AND",
12
+ matchers: [
13
+ {
14
+ keySelector: nil,
15
+ matcherType: "ALL_KEYS",
16
+ negate: false,
17
+ userDefinedSegmentMatcherData: nil,
18
+ whitelistMatcherData: nil,
19
+ unaryNumericMatcherData: nil,
20
+ betweenMatcherData: nil,
21
+ dependencyMatcherData: nil,
22
+ booleanMatcherData: nil,
23
+ stringMatcherData: nil
24
+ }]
25
+ }
26
+ }]
27
+ TILL_PREFIX = '.rbsegments.till'
28
+ RB_SEGMENTS_PREFIX = '.rbsegment.'
29
+ REGISTERED_PREFIX = '.segments.registered'
30
+
31
+ def initialize(config, internal_events_queue)
32
+ super(config)
33
+ @adapter = case @config.cache_adapter.class.to_s
34
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
35
+ SplitIoClient::Cache::Adapters::CacheAdapter.new(@config)
36
+ else
37
+ @config.cache_adapter
38
+ end
39
+ unless @config.mode.equal?(:consumer)
40
+ @adapter.set_string(namespace_key(TILL_PREFIX), '-1')
41
+ @adapter.initialize_map(namespace_key(REGISTERED_PREFIX))
42
+ end
43
+ @internal_events_queue = internal_events_queue
44
+ end
45
+
46
+ def update(to_add, to_delete, new_change_number)
47
+ to_add.each{ |rule_based_segment| add_rule_based_segment(rule_based_segment) }
48
+ to_delete.each{ |rule_based_segment| remove_rule_based_segment(rule_based_segment) }
49
+ set_change_number(new_change_number)
50
+
51
+ if to_add.length > 0 || to_delete.length > 0
52
+ @internal_events_queue.push(
53
+ SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
54
+ SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED,
55
+ SplitIoClient::Engine::Models::EventsMetadata.new(
56
+ SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE,
57
+ []
58
+ )
59
+ )
60
+ )
61
+ end
62
+ end
63
+
64
+ def get_rule_based_segment(name)
65
+ rule_based_segment = @adapter.string(namespace_key("#{RB_SEGMENTS_PREFIX}#{name}"))
66
+
67
+ JSON.parse(rule_based_segment, symbolize_names: true) if rule_based_segment
68
+ end
69
+
70
+ def rule_based_segment_names
71
+ @adapter.find_strings_by_prefix(namespace_key(RB_SEGMENTS_PREFIX))
72
+ .map { |rule_based_segment_names| rule_based_segment_names.gsub(namespace_key(RB_SEGMENTS_PREFIX), '') }
73
+ end
74
+
75
+ def set_change_number(since)
76
+ @adapter.set_string(namespace_key(TILL_PREFIX), since)
77
+ end
78
+
79
+ def get_change_number
80
+ @adapter.string(namespace_key(TILL_PREFIX))
81
+ end
82
+
83
+ def set_segment_names(names)
84
+ return if names.nil? || names.empty?
85
+
86
+ names.each do |name|
87
+ @adapter.add_to_set(namespace_key(REGISTERED_PREFIX), name)
88
+ end
89
+ end
90
+
91
+ def exists?(name)
92
+ @adapter.exists?(namespace_key("#{RB_SEGMENTS_PREFIX}#{name}"))
93
+ end
94
+
95
+ def clear
96
+ @adapter.clear(namespace_key)
97
+ end
98
+
99
+ def contains?(segment_names)
100
+ return false if rule_based_segment_names.empty?
101
+
102
+ return segment_names.to_set.subset?(rule_based_segment_names.to_set)
103
+ end
104
+
105
+ private
106
+
107
+ def add_rule_based_segment(rule_based_segment)
108
+ return unless rule_based_segment[:name]
109
+
110
+ if check_undefined_matcher(rule_based_segment)
111
+ @config.logger.warn("Rule based segment #{rule_based_segment[:name]} has undefined matcher, setting conditions to default template.")
112
+ rule_based_segment[:conditions] = RuleBasedSegmentsRepository::DEFAULT_CONDITIONS_TEMPLATE
113
+ end
114
+
115
+ @adapter.set_string(namespace_key("#{RB_SEGMENTS_PREFIX}#{rule_based_segment[:name]}"), rule_based_segment.to_json)
116
+ end
117
+
118
+ def check_undefined_matcher(rule_based_segment)
119
+ for condition in rule_based_segment[:conditions]
120
+ for matcher in condition[:matcherGroup][:matchers]
121
+ if !SplitIoClient::Condition.instance_methods(false).map(&:to_s).include?("matcher_#{matcher[:matcherType].downcase}")
122
+ @config.logger.error("Detected undefined matcher #{matcher[:matcherType].downcase} in feature flag #{rule_based_segment[:name]}")
123
+ return true
124
+ end
125
+ end
126
+ end
127
+ return false
128
+ end
129
+
130
+ def remove_rule_based_segment(rule_based_segment)
131
+ @adapter.delete(namespace_key("#{RB_SEGMENTS_PREFIX}#{rule_based_segment[:name]}"))
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -6,14 +6,16 @@ module SplitIoClient
6
6
 
7
7
  attr_reader :adapter
8
8
 
9
- def initialize(adapter)
10
- @adapter = case adapter.class.to_s
9
+ def initialize(config, internal_events_queue)
10
+ super(config)
11
+ @adapter = case @config.cache_adapter.class.to_s
11
12
  when 'SplitIoClient::Cache::Adapters::RedisAdapter'
12
- SplitIoClient::Cache::Adapters::CacheAdapter.new(adapter)
13
+ SplitIoClient::Cache::Adapters::CacheAdapter.new(@config)
13
14
  else
14
- adapter
15
+ @config.cache_adapter
15
16
  end
16
- @adapter.set_bool(namespace_key('.ready'), false) unless SplitIoClient.configuration.mode.equal?(:consumer)
17
+ @adapter.set_bool(namespace_key('.ready'), false) unless @config.mode.equal?(:consumer)
18
+ @internal_events_queue = internal_events_queue
17
19
  end
18
20
 
19
21
  # Receives segment data, adds and removes segements from the store
@@ -21,9 +23,20 @@ module SplitIoClient
21
23
  name = segment[:name]
22
24
 
23
25
  @adapter.initialize_set(segment_data(name)) unless @adapter.exists?(segment_data(name))
24
-
26
+ @config.logger.debug(segment)
25
27
  add_keys(name, segment[:added])
26
28
  remove_keys(name, segment[:removed])
29
+ if segment[:added].length > 0 || segment[:removed].length > 0
30
+ @internal_events_queue.push(
31
+ SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
32
+ SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED,
33
+ SplitIoClient::Engine::Models::EventsMetadata.new(
34
+ SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE,
35
+ []
36
+ )
37
+ )
38
+ )
39
+ end
27
40
  end
28
41
 
29
42
  def get_segment_keys(name)
@@ -62,6 +75,33 @@ module SplitIoClient
62
75
  @adapter.clear(namespace_key)
63
76
  end
64
77
 
78
+ def segments_count
79
+ used_segment_names.length
80
+ end
81
+
82
+ def segment_keys_count
83
+ names = used_segment_names
84
+
85
+ keys = 0
86
+
87
+ names.each do |name|
88
+ segment_keys = get_segment_keys(name)
89
+ keys += segment_keys.length
90
+ end
91
+
92
+ keys
93
+ rescue StandardError => e
94
+ @config.log_found_exception(__method__.to_s, e)
95
+ 0
96
+ end
97
+
98
+ def contains?(segment_names)
99
+ if segment_names.empty?
100
+ return false
101
+ end
102
+ return segment_names.to_set.subset?(used_segment_names.to_set)
103
+ end
104
+
65
105
  private
66
106
 
67
107
  def segment_data(name)
@@ -5,43 +5,67 @@ module SplitIoClient
5
5
  module Repositories
6
6
  class SplitsRepository < Repository
7
7
  attr_reader :adapter
8
+ DEFAULT_CONDITIONS_TEMPLATE = [{
9
+ conditionType: "ROLLOUT",
10
+ matcherGroup: {
11
+ combiner: "AND",
12
+ matchers: [
13
+ {
14
+ keySelector: nil,
15
+ matcherType: "ALL_KEYS",
16
+ negate: false,
17
+ userDefinedSegmentMatcherData: nil,
18
+ whitelistMatcherData: nil,
19
+ unaryNumericMatcherData: nil,
20
+ betweenMatcherData: nil,
21
+ dependencyMatcherData: nil,
22
+ booleanMatcherData: nil,
23
+ stringMatcherData: nil
24
+ }]
25
+ },
26
+ partitions: [
27
+ {
28
+ treatment: "control",
29
+ size: 100
30
+ }
31
+ ],
32
+ label: "targeting rule type unsupported by sdk"
33
+ }]
34
+ TILL_PREFIX = '.splits.till'
35
+ SPLIT_PREFIX = '.split.'
36
+ READY_PREFIX = '.splits.ready'
8
37
 
9
- def initialize(adapter)
10
- @adapter = case adapter.class.to_s
38
+ def initialize(config, flag_sets_repository, flag_set_filter, internal_events_queue)
39
+ super(config)
40
+ @tt_cache = {}
41
+ @adapter = case @config.cache_adapter.class.to_s
11
42
  when 'SplitIoClient::Cache::Adapters::RedisAdapter'
12
- SplitIoClient::Cache::Adapters::CacheAdapter.new(adapter)
43
+ SplitIoClient::Cache::Adapters::CacheAdapter.new(@config)
13
44
  else
14
- adapter
15
- end
16
- unless SplitIoClient.configuration.mode.equal?(:consumer)
17
- @adapter.set_string(namespace_key('.splits.till'), '-1')
18
- @adapter.initialize_map(namespace_key('.segments.registered'))
45
+ @config.cache_adapter
19
46
  end
47
+ @flag_sets = flag_sets_repository
48
+ @flag_set_filter = flag_set_filter
49
+ @internal_events_queue = internal_events_queue
50
+ initialize_keys
20
51
  end
21
52
 
22
- def add_split(split)
23
- return unless split[:name]
24
-
25
- @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json)
26
- end
53
+ def update(to_add, to_delete, new_change_number)
54
+ to_add.each{ |feature_flag| add_feature_flag(feature_flag) }
55
+ to_delete.each{ |feature_flag| remove_feature_flag(feature_flag) }
56
+ set_change_number(new_change_number)
27
57
 
28
- def remove_split(name)
29
- @adapter.delete(namespace_key(".split.#{name}"))
30
- end
31
-
32
- def get_splits(names)
33
- splits = {}
34
- split_names = names.map { |name| namespace_key(".split.#{name}") }
35
- splits.merge!(
36
- @adapter
37
- .multiple_strings(split_names)
38
- .map { |name, data| [name.gsub(namespace_key('.split.'), ''), data] }.to_h
39
- )
40
-
41
- splits.map do |name, data|
42
- parsed_data = data ? JSON.parse(data, symbolize_names: true) : nil
43
- [name.to_sym, parsed_data]
44
- end.to_h
58
+ if to_add.length > 0 || to_delete.length > 0
59
+ @internal_events_queue.push(
60
+ SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
61
+ SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
62
+ SplitIoClient::Engine::Models::EventsMetadata.new(
63
+ SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
64
+ to_add.map {|flag| flag[:name]} | to_delete.map {|flag| flag[:name]}
65
+ )
66
+ )
67
+ )
68
+ end
45
69
  end
46
70
 
47
71
  def get_split(name)
@@ -50,28 +74,42 @@ module SplitIoClient
50
74
  JSON.parse(split, symbolize_names: true) if split
51
75
  end
52
76
 
53
- def splits
54
- splits_hash = {}
55
-
56
- split_names.each do |name|
57
- splits_hash[name] = get_split(name)
77
+ def splits(filtered_names=nil)
78
+ symbolize = true
79
+ if filtered_names.nil?
80
+ filtered_names = split_names
81
+ symbolize = false
58
82
  end
83
+ get_splits(filtered_names, symbolize)
84
+ end
59
85
 
60
- splits_hash
86
+ def traffic_type_exists(tt_name)
87
+ case @adapter
88
+ when SplitIoClient::Cache::Adapters::CacheAdapter
89
+ tt_count = @adapter.string(namespace_key(".trafficType.#{tt_name}"))
90
+ begin
91
+ !tt_count.nil? && Integer(tt_count, 10) > 0
92
+ rescue StandardError => e
93
+ @config.logger.error("Error while parsing Traffic Type count: #{e.message}")
94
+ false
95
+ end
96
+ else
97
+ @tt_cache.key?(tt_name) && @tt_cache[tt_name] > 0
98
+ end
61
99
  end
62
100
 
63
101
  # Return an array of Split Names excluding control keys like splits.till
64
102
  def split_names
65
- @adapter.find_strings_by_prefix(namespace_key('.split.'))
66
- .map { |split| split.gsub(namespace_key('.split.'), '') }
103
+ @adapter.find_strings_by_prefix(namespace_key(SPLIT_PREFIX))
104
+ .map { |split| split.gsub(namespace_key(SPLIT_PREFIX), '') }
67
105
  end
68
106
 
69
107
  def set_change_number(since)
70
- @adapter.set_string(namespace_key('.splits.till'), since)
108
+ @adapter.set_string(namespace_key(TILL_PREFIX), since)
71
109
  end
72
110
 
73
111
  def get_change_number
74
- @adapter.string(namespace_key('.splits.till'))
112
+ @adapter.string(namespace_key(TILL_PREFIX))
75
113
  end
76
114
 
77
115
  def set_segment_names(names)
@@ -87,19 +125,170 @@ module SplitIoClient
87
125
  end
88
126
 
89
127
  def ready?
90
- @adapter.string(namespace_key('.splits.ready')).to_i != -1
128
+ @adapter.string(namespace_key(READY_PREFIX)).to_i != -1
91
129
  end
92
130
 
93
131
  def not_ready!
94
- @adapter.set_string(namespace_key('.splits.ready'), -1)
132
+ @adapter.set_string(namespace_key(READY_PREFIX), -1)
95
133
  end
96
134
 
97
135
  def ready!
98
- @adapter.set_string(namespace_key('.splits.ready'), Time.now.utc.to_i)
136
+ @adapter.set_string(namespace_key(READY_PREFIX), Time.now.utc.to_i)
99
137
  end
100
138
 
101
139
  def clear
140
+ @tt_cache.clear
141
+
102
142
  @adapter.clear(namespace_key)
143
+ initialize_keys
144
+ end
145
+
146
+ def kill(change_number, split_name, default_treatment)
147
+ split = get_split(split_name)
148
+
149
+ return if split.nil?
150
+
151
+ split[:killed] = true
152
+ split[:defaultTreatment] = default_treatment
153
+ split[:changeNumber] = change_number
154
+
155
+ @adapter.set_string(namespace_key(".split.#{split_name}"), split.to_json)
156
+ @internal_events_queue.push(
157
+ SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
158
+ SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION,
159
+ SplitIoClient::Engine::Models::EventsMetadata.new(
160
+ SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
161
+ [split_name]
162
+ )
163
+ )
164
+ )
165
+ end
166
+
167
+ def splits_count
168
+ split_names.length
169
+ end
170
+
171
+ def get_feature_flags_by_sets(flag_sets)
172
+ sets_to_fetch = Array.new
173
+ flag_sets.each do |flag_set|
174
+ unless @flag_sets.flag_set_exist?(flag_set)
175
+ @config.logger.warn("Flag set #{flag_set} is not part of the configured flag set list, ignoring it.")
176
+ next
177
+ end
178
+ sets_to_fetch.push(flag_set)
179
+ end
180
+ @flag_sets.get_flag_sets(flag_sets)
181
+ end
182
+
183
+ def is_flag_set_exist(flag_set)
184
+ @flag_sets.flag_set_exist?(flag_set)
185
+ end
186
+
187
+ def flag_set_filter
188
+ @flag_set_filter
189
+ end
190
+
191
+ private
192
+
193
+ def initialize_keys
194
+ unless @config.mode.equal?(:consumer)
195
+ @adapter.set_string(namespace_key(TILL_PREFIX), '-1')
196
+ @adapter.initialize_map(namespace_key('.segments.registered'))
197
+ end
198
+ end
199
+
200
+ def add_feature_flag(split)
201
+ return unless split[:name]
202
+ existing_split = get_split(split[:name])
203
+
204
+ if(!existing_split)
205
+ increase_tt_name_count(split[:trafficTypeName])
206
+ elsif(existing_split[:trafficTypeName] != split[:trafficTypeName])
207
+ increase_tt_name_count(split[:trafficTypeName])
208
+ decrease_tt_name_count(existing_split[:trafficTypeName])
209
+ remove_from_flag_sets(existing_split)
210
+ elsif(existing_split[:sets] != split[:sets])
211
+ remove_from_flag_sets(existing_split)
212
+ end
213
+
214
+ if check_undefined_matcher(split)
215
+ @config.logger.warn("Feature Flag #{split[:name]} has undefined matcher, setting conditions to default template.")
216
+ split[:conditions] = SplitsRepository::DEFAULT_CONDITIONS_TEMPLATE
217
+ end
218
+ if !split[:sets].nil?
219
+ for flag_set in split[:sets]
220
+ if !@flag_sets.flag_set_exist?(flag_set)
221
+ if @flag_set_filter.should_filter?
222
+ next
223
+ end
224
+ @flag_sets.add_flag_set(flag_set)
225
+ end
226
+ @flag_sets.add_feature_flag_to_flag_set(flag_set, split[:name])
227
+ end
228
+ end
229
+
230
+ @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json)
231
+ end
232
+
233
+ def check_undefined_matcher(split)
234
+ for condition in split[:conditions]
235
+ for matcher in condition[:matcherGroup][:matchers]
236
+ if !SplitIoClient::Condition.instance_methods(false).map(&:to_s).include?("matcher_#{matcher[:matcherType].downcase}")
237
+ @config.logger.error("Detected undefined matcher #{matcher[:matcherType].downcase} in feature flag #{split[:name]}")
238
+ return true
239
+ end
240
+ end
241
+ end
242
+ return false
243
+ end
244
+
245
+ def remove_feature_flag(split)
246
+ decrease_tt_name_count(split[:trafficTypeName])
247
+ remove_from_flag_sets(split)
248
+ @adapter.delete(namespace_key(".split.#{split[:name]}"))
249
+ end
250
+
251
+ def get_splits(names, symbolize_names = true)
252
+ splits = {}
253
+ split_names = names.map { |name| namespace_key(".split.#{name}") }
254
+ splits.merge!(
255
+ @adapter
256
+ .multiple_strings(split_names)
257
+ .map { |name, data| [name.gsub(namespace_key('.split.'), ''), data] }.to_h
258
+ )
259
+
260
+ splits.map do |name, data|
261
+ parsed_data = data ? JSON.parse(data, symbolize_names: true) : nil
262
+ split_name = symbolize_names ? name.to_sym : name
263
+ [split_name, parsed_data]
264
+ end.to_h
265
+ end
266
+
267
+ def remove_from_flag_sets(feature_flag)
268
+ name = feature_flag[:name]
269
+ flag_sets = get_split(name)[:sets] if exists?(name)
270
+ if !flag_sets.nil?
271
+ for flag_set in flag_sets
272
+ @flag_sets.remove_feature_flag_from_flag_set(flag_set, feature_flag[:name])
273
+ if is_flag_set_exist(flag_set) && @flag_sets.get_flag_sets([flag_set]).length == 0 && !@flag_set_filter.should_filter?
274
+ @flag_sets.remove_flag_set(flag_set)
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ def increase_tt_name_count(tt_name)
281
+ return unless tt_name
282
+
283
+ @tt_cache[tt_name] = 0 unless @tt_cache[tt_name]
284
+ @tt_cache[tt_name] += 1
285
+ end
286
+
287
+ def decrease_tt_name_count(tt_name)
288
+ return unless tt_name
289
+
290
+ @tt_cache[tt_name] -= 1 if @tt_cache[tt_name]
291
+ @tt_cache.delete(tt_name) if @tt_cache[tt_name] == 0
103
292
  end
104
293
  end
105
294
  end
@@ -2,8 +2,9 @@ module SplitIoClient
2
2
  class ImpressionRouter
3
3
  attr_reader :router_thread
4
4
 
5
- def initialize
6
- @listener = SplitIoClient.configuration.impression_listener
5
+ def initialize(config)
6
+ @config = config
7
+ @listener = @config.impression_listener
7
8
 
8
9
  return unless @listener
9
10
 
@@ -17,39 +18,40 @@ module SplitIoClient
17
18
  end
18
19
  end
19
20
 
20
- def add(impression)
21
- enqueue(impression)
22
- end
23
-
24
21
  def add_bulk(impressions)
25
- impressions[:split_names].each do |split_name|
26
- enqueue(
27
- split_name: split_name.to_s,
28
- matching_key: impressions[:matching_key],
29
- bucketing_key: impressions[:bucketing_key],
30
- treatment: {
31
- label: impressions[:treatments_labels_change_numbers][split_name.to_sym][:label],
32
- treatment: impressions[:treatments_labels_change_numbers][split_name.to_sym][:treatment],
33
- change_number: impressions[:treatments_labels_change_numbers][split_name.to_sym][:change_number]
34
- },
35
- attributes: impressions[:attributes]
36
- )
22
+ impressions.each do |impression|
23
+ enqueue(impression)
37
24
  end
38
25
  end
39
26
 
40
27
  private
41
28
 
42
29
  def enqueue(impression)
43
- @queue.push(impression) if @listener
30
+ imp = {
31
+ split_name: impression[:i][:f],
32
+ matching_key: impression[:i][:k],
33
+ bucketing_key: impression[:i][:b],
34
+ time: impression[:i][:m],
35
+ treatment: {
36
+ label: impression[:i][:r],
37
+ treatment: impression[:i][:t],
38
+ change_number: impression[:i][:c]
39
+ },
40
+ previous_time: impression[:i][:pt],
41
+ attributes: impression[:attributes]
42
+ }
43
+ @queue.push(imp) if @listener
44
+ rescue StandardError => e
45
+ @config.log_found_exception(__method__.to_s, e)
44
46
  end
45
47
 
46
48
  def router_thread
47
- SplitIoClient.configuration.threads[:impression_router] = Thread.new do
49
+ @config.threads[:impression_router] = Thread.new do
48
50
  loop do
49
51
  begin
50
52
  @listener.log(@queue.pop)
51
- rescue StandardError => error
52
- SplitIoClient.configuration.log_found_exception(__method__.to_s, error)
53
+ rescue StandardError => e
54
+ @config.log_found_exception(__method__.to_s, e)
53
55
  end
54
56
  end
55
57
  end