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.
- checksums.yaml +4 -4
- data/CHANGES.txt +10 -2
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +2 -29
- data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
- data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
- data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +98 -39
- data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +1 -1
- data/lib/splitclient-rb/clients/split_client.rb +166 -82
- data/lib/splitclient-rb/engine/api/splits.rb +9 -3
- data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +1 -1
- data/lib/splitclient-rb/engine/parser/evaluator.rb +15 -21
- data/lib/splitclient-rb/exceptions.rb +11 -0
- data/lib/splitclient-rb/helpers/repository_helper.rb +23 -0
- data/lib/splitclient-rb/managers/split_manager.rb +3 -1
- data/lib/splitclient-rb/split_config.rb +22 -6
- data/lib/splitclient-rb/split_factory.rb +32 -9
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +4 -9
- data/lib/splitclient-rb/telemetry/domain/constants.rb +4 -0
- data/lib/splitclient-rb/telemetry/domain/structs.rb +4 -4
- data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +18 -2
- data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +0 -1
- data/lib/splitclient-rb/telemetry/storages/memory.rb +12 -0
- data/lib/splitclient-rb/telemetry/synchronizer.rb +6 -2
- data/lib/splitclient-rb/validators.rb +63 -3
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +4 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b094c4e9aa55d91ee6c3f426b6ff996d562aa30
|
4
|
+
data.tar.gz: 5ac15be553c82b328977d4017692e07e33cd0873
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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]
|
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
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
|