splitclient-rb 8.2.0-java → 8.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.txt +10 -2
  3. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +2 -29
  4. data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
  5. data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
  6. data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
  7. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +98 -39
  8. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +1 -1
  9. data/lib/splitclient-rb/clients/split_client.rb +166 -82
  10. data/lib/splitclient-rb/engine/api/splits.rb +9 -3
  11. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +1 -1
  12. data/lib/splitclient-rb/engine/parser/evaluator.rb +15 -21
  13. data/lib/splitclient-rb/exceptions.rb +11 -0
  14. data/lib/splitclient-rb/helpers/repository_helper.rb +23 -0
  15. data/lib/splitclient-rb/managers/split_manager.rb +3 -1
  16. data/lib/splitclient-rb/split_config.rb +22 -6
  17. data/lib/splitclient-rb/split_factory.rb +32 -9
  18. data/lib/splitclient-rb/sse/workers/splits_worker.rb +4 -9
  19. data/lib/splitclient-rb/telemetry/domain/constants.rb +4 -0
  20. data/lib/splitclient-rb/telemetry/domain/structs.rb +4 -4
  21. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +18 -2
  22. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +0 -1
  23. data/lib/splitclient-rb/telemetry/storages/memory.rb +12 -0
  24. data/lib/splitclient-rb/telemetry/synchronizer.rb +6 -2
  25. data/lib/splitclient-rb/validators.rb +63 -3
  26. data/lib/splitclient-rb/version.rb +1 -1
  27. data/lib/splitclient-rb.rb +4 -0
  28. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea3da7680876860f77e069d5f693d11d3fb4c2de
4
- data.tar.gz: 16046200f0b3b965a3518cafbd12aba47ede4c6b
3
+ metadata.gz: 4b094c4e9aa55d91ee6c3f426b6ff996d562aa30
4
+ data.tar.gz: 5ac15be553c82b328977d4017692e07e33cd0873
5
5
  SHA512:
6
- metadata.gz: 9b442d89bf1108f8af055a99b5c81b88357cad7cd67637b3f8945a17fa84f74a6f40d85de43acb12225183f5382622ade374ec20c8614495122c29e34a885d91
7
- data.tar.gz: 3b89a1134ee5f2bfcb47604bce63e76b05b9b8ca63d442bf70573a43817619b959c5b73a58ea74109dbb16298706c8e2266326276668497ad4ef749c014c1a40
6
+ metadata.gz: bfa9547c2fe3ca425f7a56091e6982ad2043fc9cfe9dfdbb3f3d9f1a28cd437f556b1863667616587a277cc8673f44333138cfd3c0463775cb1992376752596f
7
+ data.tar.gz: be87ee1ab9f5c231481a1ee0966b5f31d47e7ef2b38d5467a39457ee89db83143912e19721f51745b69b578e23359077adeffdf32c41475459d915bf380acf80
data/CHANGES.txt CHANGED
@@ -1,4 +1,12 @@
1
1
  CHANGES
2
+ 8.3.0 (Dec 11, 2023)
3
+ - Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation):
4
+ - Added new variations of the get treatment methods to support evaluating flags in given flag set/s.
5
+ - get_treatments_by_flag_set and get_treatments_by_flag_sets
6
+ - get_treatments_with_config_by_flag_set and get_treatments_with_config_by_flag_sets
7
+ - Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload.
8
+ - Note: Only applicable when the SDK is in charge of the rollout data synchronization. When not applicable, the SDK will log a warning on init.
9
+ - Added `default_treatment` and `sets` property to the `split_view` object returned by the `split` and `splits` methods of the SDK manager.
2
10
 
3
11
  8.2.0 (Jul 18, 2023)
4
12
  - Improved streaming architecture implementation to apply feature flag updates from the notification received which is now enhanced, improving efficiency and reliability of the whole update system.
@@ -60,10 +68,10 @@ CHANGES
60
68
 
61
69
  7.2.1 (Oct 23, 2020)
62
70
  - Updated redis dependency to >= 4.2.2.
63
- - Updated ably error handling.
71
+ - Updated ably error handling.
64
72
 
65
73
  7.2.0 (Sep 25, 2020)
66
- - Added impressions dedupe logic to avoid sending duplicated impressions:
74
+ - Added impressions dedupe logic to avoid sending duplicated impressions:
67
75
  - Added `OPTIMIZED` and `DEBUG` modes in order to enabling/disabling how impressions are going to be sent into Split servers,
68
76
  - `OPTIMIZED`: will send unique impressions in a timeframe in order to reduce how many times impressions are posted to Split.
69
77
  - `DEBUG`: will send every impression generated to Split.
@@ -17,7 +17,7 @@ module SplitIoClient
17
17
  fetch_splits
18
18
  return
19
19
  end
20
-
20
+
21
21
  splits_thread
22
22
  end
23
23
 
@@ -25,13 +25,8 @@ module SplitIoClient
25
25
  @semaphore.synchronize do
26
26
  data = splits_since(@splits_repository.get_change_number, fetch_options)
27
27
 
28
- data[:splits] && data[:splits].each do |split|
29
- add_split_unless_archived(split)
30
- end
31
-
28
+ SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:splits], data[:till], @config)
32
29
  @splits_repository.set_segment_names(data[:segment_names])
33
- @splits_repository.set_change_number(data[:till])
34
-
35
30
  @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
36
31
 
37
32
  { segment_names: data[:segment_names], success: true }
@@ -64,28 +59,6 @@ module SplitIoClient
64
59
  splits_api.since(since, fetch_options)
65
60
  end
66
61
 
67
- def add_split_unless_archived(split)
68
- if Engine::Models::Split.archived?(split)
69
- @config.logger.debug("Seeing archived feature flag #{split[:name]}") if @config.debug_enabled
70
-
71
- remove_archived_split(split)
72
- else
73
- store_split(split)
74
- end
75
- end
76
-
77
- def remove_archived_split(split)
78
- @config.logger.debug("removing feature flag from store(#{split})") if @config.debug_enabled
79
-
80
- @splits_repository.remove_split(split)
81
- end
82
-
83
- def store_split(split)
84
- @config.logger.debug("storing feature flag (#{split[:name]})") if @config.debug_enabled
85
-
86
- @splits_repository.add_split(split)
87
- end
88
-
89
62
  def splits_api
90
63
  @splits_api ||= SplitIoClient::Api::Splits.new(@api_key, @config, @telemetry_runtime_producer)
91
64
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module SplitIoClient
6
+ module Cache
7
+ module Filter
8
+ class FlagSetsFilter
9
+ def initialize(flag_sets = [])
10
+ @flag_sets = Set.new(flag_sets)
11
+ @should_filter = @flag_sets.any?
12
+ end
13
+
14
+ def should_filter?
15
+ @should_filter
16
+ end
17
+
18
+ def flag_set_exist?(flag_set)
19
+ return true unless @should_filter
20
+
21
+ if not flag_set.is_a?(String) or flag_set.empty?
22
+ return false
23
+ end
24
+
25
+ @flag_sets.intersection([flag_set]).any?
26
+ end
27
+
28
+ def intersect?(flag_sets)
29
+ return true unless @should_filter
30
+
31
+ if not flag_sets.is_a?(Array) or flag_sets.empty?
32
+ return false
33
+ end
34
+
35
+ @flag_sets.intersection(Set.new(flag_sets)).any?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Repositories
6
+ class MemoryFlagSetsRepository
7
+ def initialize(flag_sets = [])
8
+ @sets_feature_flag_map = {}
9
+ flag_sets.each{ |flag_set| @sets_feature_flag_map[flag_set] = Set[] }
10
+ end
11
+
12
+ def flag_set_exist?(flag_set)
13
+ @sets_feature_flag_map.key?(flag_set)
14
+ end
15
+
16
+ def get_flag_sets(flag_sets)
17
+ to_return = Array.new
18
+ flag_sets.each { |flag_set| to_return.concat(@sets_feature_flag_map[flag_set].to_a)}
19
+ to_return.uniq
20
+ end
21
+
22
+ def add_flag_set(flag_set)
23
+ @sets_feature_flag_map[flag_set] = Set[] if !flag_set_exist?(flag_set)
24
+ end
25
+
26
+ def remove_flag_set(flag_set)
27
+ @sets_feature_flag_map.delete(flag_set) if flag_set_exist?(flag_set)
28
+ end
29
+
30
+ def add_feature_flag_to_flag_set(flag_set, feature_flag)
31
+ @sets_feature_flag_map[flag_set].add(feature_flag) if flag_set_exist?(flag_set)
32
+ end
33
+
34
+ def remove_feature_flag_from_flag_set(flag_set, feature_flag)
35
+ @sets_feature_flag_map[flag_set].delete(feature_flag) if flag_set_exist?(flag_set)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Repositories
6
+ class RedisFlagSetsRepository < Repository
7
+
8
+ def initialize(config)
9
+ super(config)
10
+ @adapter = SplitIoClient::Cache::Adapters::RedisAdapter.new(@config.redis_url)
11
+ end
12
+
13
+ def flag_set_exist?(flag_set)
14
+ @adapter.exists?(namespace_key(".flagSet.#{flag_set}"))
15
+ end
16
+
17
+ def get_flag_sets(flag_sets)
18
+ result = @adapter.redis.pipelined do |pipeline|
19
+ flag_sets.each do |flag_set|
20
+ pipeline.smembers(namespace_key(".flagSet.#{flag_set}"))
21
+ end
22
+ end
23
+ to_return = Array.new
24
+ result.each do |flag_set|
25
+ flag_set.each { |feature_flag_name| to_return.push(feature_flag_name.to_s)}
26
+ end
27
+ to_return.uniq
28
+ end
29
+
30
+ def add_flag_set(flag_set)
31
+ # not implemented
32
+ end
33
+
34
+ def remove_flag_set(flag_set)
35
+ # not implemented
36
+ end
37
+
38
+ def add_feature_flag_to_flag_set(flag_set, feature_flag)
39
+ # not implemented
40
+ end
41
+
42
+ def remove_feature_flag_from_flag_set(flag_set, feature_flag)
43
+ # not implemented
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -6,7 +6,7 @@ module SplitIoClient
6
6
  class SplitsRepository < Repository
7
7
  attr_reader :adapter
8
8
 
9
- def initialize(config)
9
+ def initialize(config, flag_sets_repository, flag_set_filter)
10
10
  super(config)
11
11
  @tt_cache = {}
12
12
  @adapter = case @config.cache_adapter.class.to_s
@@ -15,48 +15,18 @@ module SplitIoClient
15
15
  else
16
16
  @config.cache_adapter
17
17
  end
18
+ @flag_sets = flag_sets_repository
19
+ @flag_set_filter = flag_set_filter
18
20
  unless @config.mode.equal?(:consumer)
19
21
  @adapter.set_string(namespace_key('.splits.till'), '-1')
20
22
  @adapter.initialize_map(namespace_key('.segments.registered'))
21
23
  end
22
24
  end
23
25
 
24
- def add_split(split)
25
- return unless split[:name]
26
- existing_split = get_split(split[:name])
27
-
28
- if(!existing_split)
29
- increase_tt_name_count(split[:trafficTypeName])
30
- elsif(existing_split[:trafficTypeName] != split[:trafficTypeName])
31
- increase_tt_name_count(split[:trafficTypeName])
32
- decrease_tt_name_count(existing_split[:trafficTypeName])
33
- end
34
-
35
- @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json)
36
- end
37
-
38
- def remove_split(split)
39
- tt_name = split[:trafficTypeName]
40
-
41
- decrease_tt_name_count(split[:trafficTypeName])
42
-
43
- @adapter.delete(namespace_key(".split.#{split[:name]}"))
44
- end
45
-
46
- def get_splits(names, symbolize_names = true)
47
- splits = {}
48
- split_names = names.map { |name| namespace_key(".split.#{name}") }
49
- splits.merge!(
50
- @adapter
51
- .multiple_strings(split_names)
52
- .map { |name, data| [name.gsub(namespace_key('.split.'), ''), data] }.to_h
53
- )
54
-
55
- splits.map do |name, data|
56
- parsed_data = data ? JSON.parse(data, symbolize_names: true) : nil
57
- split_name = symbolize_names ? name.to_sym : name
58
- [split_name, parsed_data]
59
- end.to_h
26
+ def update(to_add, to_delete, new_change_number)
27
+ to_add.each{ |feature_flag| add_feature_flag(feature_flag) }
28
+ to_delete.each{ |feature_flag| remove_feature_flag(feature_flag) }
29
+ set_change_number(new_change_number)
60
30
  end
61
31
 
62
32
  def get_split(name)
@@ -65,8 +35,13 @@ module SplitIoClient
65
35
  JSON.parse(split, symbolize_names: true) if split
66
36
  end
67
37
 
68
- def splits
69
- get_splits(split_names, false)
38
+ def splits(filtered_names=nil)
39
+ symbolize = true
40
+ if filtered_names.nil?
41
+ filtered_names = split_names
42
+ symbolize = false
43
+ end
44
+ get_splits(filtered_names, symbolize)
70
45
  end
71
46
 
72
47
  def traffic_type_exists(tt_name)
@@ -144,8 +119,92 @@ module SplitIoClient
144
119
  split_names.length
145
120
  end
146
121
 
122
+ def get_feature_flags_by_sets(flag_sets)
123
+ sets_to_fetch = Array.new
124
+ flag_sets.each do |flag_set|
125
+ unless @flag_sets.flag_set_exist?(flag_set)
126
+ @config.logger.warn("Flag set #{flag_set} is not part of the configured flag set list, ignoring it.")
127
+ next
128
+ end
129
+ sets_to_fetch.push(flag_set)
130
+ end
131
+ @flag_sets.get_flag_sets(flag_sets)
132
+ end
133
+
134
+ def is_flag_set_exist(flag_set)
135
+ @flag_sets.flag_set_exist?(flag_set)
136
+ end
137
+
138
+ def flag_set_filter
139
+ @flag_set_filter
140
+ end
141
+
147
142
  private
148
143
 
144
+ def add_feature_flag(split)
145
+ return unless split[:name]
146
+ existing_split = get_split(split[:name])
147
+
148
+ if(!existing_split)
149
+ increase_tt_name_count(split[:trafficTypeName])
150
+ elsif(existing_split[:trafficTypeName] != split[:trafficTypeName])
151
+ increase_tt_name_count(split[:trafficTypeName])
152
+ decrease_tt_name_count(existing_split[:trafficTypeName])
153
+ remove_from_flag_sets(existing_split)
154
+ elsif(existing_split[:sets] != split[:sets])
155
+ remove_from_flag_sets(existing_split)
156
+ end
157
+
158
+ if !split[:sets].nil?
159
+ for flag_set in split[:sets]
160
+ if !@flag_sets.flag_set_exist?(flag_set)
161
+ if @flag_set_filter.should_filter?
162
+ next
163
+ end
164
+ @flag_sets.add_flag_set(flag_set)
165
+ end
166
+ @flag_sets.add_feature_flag_to_flag_set(flag_set, split[:name])
167
+ end
168
+ end
169
+
170
+ @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json)
171
+ end
172
+
173
+ def remove_feature_flag(split)
174
+ decrease_tt_name_count(split[:trafficTypeName])
175
+ remove_from_flag_sets(split)
176
+ @adapter.delete(namespace_key(".split.#{split[:name]}"))
177
+ end
178
+
179
+ def get_splits(names, symbolize_names = true)
180
+ splits = {}
181
+ split_names = names.map { |name| namespace_key(".split.#{name}") }
182
+ splits.merge!(
183
+ @adapter
184
+ .multiple_strings(split_names)
185
+ .map { |name, data| [name.gsub(namespace_key('.split.'), ''), data] }.to_h
186
+ )
187
+
188
+ splits.map do |name, data|
189
+ parsed_data = data ? JSON.parse(data, symbolize_names: true) : nil
190
+ split_name = symbolize_names ? name.to_sym : name
191
+ [split_name, parsed_data]
192
+ end.to_h
193
+ end
194
+
195
+ def remove_from_flag_sets(feature_flag)
196
+ name = feature_flag[:name]
197
+ flag_sets = get_split(name)[:sets] if exists?(name)
198
+ if !flag_sets.nil?
199
+ for flag_set in flag_sets
200
+ @flag_sets.remove_feature_flag_from_flag_set(flag_set, feature_flag[:name])
201
+ if is_flag_set_exist(flag_set) && @flag_sets.get_flag_sets([flag_set]).length == 0 && !@flag_set_filter.should_filter?
202
+ @flag_sets.remove_flag_set(flag_set)
203
+ end
204
+ end
205
+ end
206
+ end
207
+
149
208
  def increase_tt_name_count(tt_name)
150
209
  return unless tt_name
151
210
 
@@ -55,7 +55,7 @@ module SplitIoClient
55
55
  def store_split(split)
56
56
  @config.logger.debug("storing feature flag (#{split[:name]})") if @config.debug_enabled
57
57
 
58
- @splits_repository.add_split(split)
58
+ @splits_repository.update([split], [], -1)
59
59
  end
60
60
 
61
61
  def load_features